java并发复习笔记总结
本文地址:http://www.cnblogs.com/maplefighting/p/7941885.html
1、volatile:轻量级的synchronized,不会引起线程上下问切换
为了提高速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再操作。声明了volatile,jvm向处理器大宋Lock前缀指令,将变量在缓存行的数据写到系统内存,每个处理器通过总线传播的数据检查自己缓存的值是否过期。
2、volatile实现原则:
(1) Lock前缀指令会引起处理器缓存回写到内存。
(2) 一个处理器的缓存回写到内存会导致其他处理器的缓存失效。
3、锁的状态,级别从低到高为:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态
锁可以升级但不能降级
偏向锁:只有一个线程进入临界区
轻量级锁:多个线程交替进入临界区 CAS
重量级锁:多个线程同时进入临界区
4、处理器实现原子操作:(1) 总线锁 (2) 缓存锁定
5、Java实现原子操作:(1) 锁 (2) 循环CAS
6、volatile:写happens-before 读
写一个volatile变量时,JMM (Java内存模型) 会把该线程对应的本地内存中的共享变量刷新到主内存中。
读一个volatile变量时,JMM会把本地内存置无效,从内存读取共享变量。
7、CAS:先操作比较与预期的值是否一样,一样就设置,不一样就继续循环(CompareAndSet)
CAS同时具有volatile读与写到内存语义
8、happens-before 先行规则
(1) 程序顺序规则:一个线程的每个操作happens-before于该线程中的任意后序操作
(2) 监视器锁规则:对一个锁的解锁happens-before于任意后序对这个锁的加锁
(3) volatole变量规则:对一个volatile域的写happens-brfore于读
(4) 传递性:A happens-before B,B happens-before C => A happens-before C
(5) start()规则:如果A执行 ThreadB.start(),那么 A线程的ThreadB.start() happens-before B的任意操作
(6) join()规则:如果A执行ThreadB.join()并成功返回,那么B中任意操作happens-before 于A从 ThreadB,join()操作成功返回
9、上下文切换:任务从保存到加载的过程
减少上下文切换有无锁并发编程,CAS算法,使用最少线程和使用协程
10、Java内存模型
线程之间的共享变量存储在主内存中,每个内存都有一个私有的本地内存,本地内存存储了该线程读写共享变量的副本,本地内存只是一抽象,并不真实存在。
11、final 内存语义
(1) 在构造函数内对final 域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,不能重排序。
(2) 初次读一个包含final域对象的引用,与随后初次读这个fianl域,不能重排序。
12、as-if-serial 语义保证单线程内程序的执行结果不被改变
happens-before 保证正确同步的多线程程序执行结果不被改变
13、HashMap
jdk1.7多线程put会死循环,因为Entry链表形成环数据结构,Entry的next永远不为空
没使用Synchronized,可接受 null 的 key 和 value
& 默认容量为16,扩容2倍 自定义的hash
HashTable
使用 Synchronized,key 和value 都不能 null
% 默认容量为11,扩容两倍+1 hashcode
ConcurrentHashMap
段分锁,提高并发访问效率
segment->可重入锁,2^n长度 包含HashEntry的链表结构
14、ConcurrentHashMap
(1) 由 Segment 和 HashEntry 构成 , Segment长度为2的n次方,jdk1.7 Segment会锁住多个HashEntry
(2) get:先经过一次再散列,根据值使用散列运算定位到Segment,再通过散列算法定位到元素。get里面的共享变量都定义为volatile类型,所以不用加锁
(3)定位HashEntry 和定位Segment 都是与数组长度-1 相与,但是相与的"值"不一样。Segment使用的是元素的hashcode 通过再散列后的值的高位,定位HashEntry 直接使用再散列后的值。目的:避免两次值一样
(4) put 必须加锁,定位到Segment ,判断是否HashEntry 要扩容,然后再添加
(5) 第一次put时才初始化
15、ConcurrentHashMap
jdk1.8改进:(1)取消Segment字段,使用volatile HashEntry<K,V>[] 数组元素作为锁 (2) 改为table数组 + 单向链表 + 红黑树,节点超过8会用红黑树
jdk1.7
put:(1) A tryLock 成功获得锁,把hashEntry插入
(2)获取锁失败,执行scanAndLockForPut方法,重复执行tryLock,多处理器重复64,单处理器重复1次,超过时,挂起线程
size:(1)不加锁,计算两次,相同说明准确。不一样,给每个Segment加锁,再计算
modCount put,remove,clean时会加1
jdk1.8
size:元素个数保存在baseCount,部分元素的变化个数保存在CounterCell数组中,累加
a、新增节点后,链表的元素个数达到8,就转换成红黑树,不过转换之前,如果数组长度小于阀值,默认为64 (数组长度大于64,才会考虑转换红黑树),则会扩大两倍,并触发transfer
b、元素个数0.75倍数组,扩容 (hashMap也一样)
16、ReentrantLock 可重入锁,分为公平锁和非公平锁
a、公平锁:队列顺序,等待久的先
非公平锁:可以抢占 CAS
b、公平锁和非公平锁,释放最后都要写一个volatile变量
公平锁,获取时会先读volatile变量。非公平锁获取时,会用CAS更新volatile变量
c、非公平锁用CAS。 公平锁加个判读去年当前节点是否有前驱节点 hasQueuedPredecessors()方法
d、非公平锁可能造成线程"饥饿",但是极少的线程切换,保证吞吐量,吞吐量更大。
17、ReetrantReadWriteLock读写锁
高16位读,低16位写 AQS 可重入
锁降级:把持当前拥有的写锁,再获取读锁,稍后释放锁。目的:保证数据的可见性
18、队列同步器 AQS (AbstractQueuedSynchronizer) 构建锁和或其他同步组件的基础架构 ReetrantLock,Condition,ReetrantReadWriteLock等
同步器提供 getState(),setState( .... ),conpareAndSetState( .... )
重写AQS时,可以使用CAS等
实现:a、同步队列 双向链表 ,CAS设置尾节点
b、独占式同步状态获取与释放 CAS入队enq(node),出队时,每个node都在自旋
c、共享式 <-- 释放同步线程CAS
内部使用volatile修饰 int state 表示同步状态
共享式同步状态:tryAcquireShared(arg) >= 0时,能获得同步状态
获取同步状态失败进入同步队列(addWaiter),先CAS设置,失败进入enq方法。出队列不需要CAS
19、ConcurrentLinkedQueue 不要用size(),会遍历全部
(1)非阻塞,入队永远返回true,用CAS
不是每次节点入队后都将tail节点更新为尾节点,而是当tail与尾节点距离>=HOPS常量时才更新,提高入队效率
出队仅没有元素时才会更新head节点,减少CAS
(2)阻塞队列
插入:队列满时阻塞 移除:队列空时阻塞
使用通知模式 condition 消费者消费通知生产者
阻塞生产者通过LockSupport.park实现
20、Condition接口 是AQS的内部类
condition定义了await()和signal(),signalAll() 方法
获取一个condition要通过Lock的newCondition。
一个condition包含一个等待队列,增加节点没有CAS保证,由await()锁保证线程安全
(1) await()时会使当前线程从同步队列首节点构造成一个新节点,加入等待队列中。
(2) 调用condition的signal() 方法时,将等待队列中首节点加到同步队列中
21、LockSupport 每个线程都有一个Park实例, unPark可以先于Park出现
基于Unsafe实现的
使用Park使线程挂起,释放(1) 其他线程调用unPark,(2) 线程中断,(3) park方法立即返回
22、Fork/Join框架:分割任务,执行并合并结果。 工作窃取算法
23、CycleBarrier 所有线程彼此等待 CountDownLatch:只要报到
允许一个或多个线程等待其他线程完成操作
CountDownLatch的计数器只使用一次
CycleBarrier可以reset(),跟join差不多(AQS,ReetrantLock)
等多有线程都运行到下一个步骤前等待
24、控制并发线程数的Semaphore (信号量) AQS
用来控制同时访问特定资源的线程数量
25、Synchronize和Lock的区别
(1) Lock的锁是Java写的控制锁的代码。 Synchronize是托管给jvm的,Java关键字
(2) Synchronize在异常时,jvm会释放锁。 Lock不会主动释放锁,要手工释放。
(3) Synchronize 悲观的排他锁。 Lock有读写锁,公平锁,非公平锁
26、线程池 ThreadPoolExecutor
好处:(1) 降低资源消耗 (2) 提高响应速度 (3) 提高线程可管理性
流程:a、判断核心线程池线程是否执行任务。 创建线程需要获取全局锁
b、是的话,判断工作队列是否已满。 目的:尽可能避免获取全局锁
c、是的话,判断线程池的线程是否都处于工作状态
c满了就调用饱和策略。默认抛出异常
提交任务:execute() 不返回值 submit() 返回future类型的对象
关闭池:shutdownNow 停止所有 shutdown 中断没有执行任务的线程
线程池最大容量 AtomicInteger类型,capacity前三位为标志位,所以最大为(2^29) - 1
状态分别为:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED
参数:corePoolSize 核心线程数 maximumPoolSize 最大线程数 keepAliveTime 最大存活时间 rejectExecutionHandler 任务拒绝处理策略
27、Executoe框架
应用框架通过Executor框架控制上层调度,而下层调度由操作系统内核控制
使用流程:主线程先创建Runnable或Callable接口,然后交给ExecutorService执行,返回Future接口对象
28、ThreadLocal
一个线程可以根据ThreadLocal查询到绑定在这个线程的一个值,可用于数据库连接
提供get和set访问与当前相关的局部变量。变量是保存在线程中的threadLocals (threadLocalMap类型的)
ThradLocalMap.Entry弱引用,随时可能被回收
get先得到当前线程的ThreadLocalMap,再根据 ××.Entry = get(this) 得到值
29、Synchroinzed:jvm基于进入与退出使用monitorenter和monitorexit指令实现,偏向锁,轻量级锁
30、atomic×××
value成员都是volatile CAS
基本方法:get/set,compareAndSet (unsafe.compareAnsSwapInt(....) )
CAS:调用UnSafe的方法,不是用Java实现,而是JNI调用操作系统原生程序
31、CopyOnWriteArrayList 占用内存
当我们往一个容器添加元素时,不直接往当前容器添加,而是先copy复制一个新的容器,添加完后,再将原容器指向新的。
并发读不用加锁。添加时要加锁,否则多线程会copy出多个副本。
如果在最后添加元素,则用Array.copyOf()。如果在中间插入,则用System.arraycopy分两段复制。
应用读多写少并发场景,如白名单,黑名单
参考书籍:Java并发编程的艺术(推荐),juc包源码
--------------------------------------------------------------------------------------------------------------
以上为maplefighting个人笔记整理,如有出错,欢迎指正