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个人笔记整理,如有出错,欢迎指正

 

posted @ 2017-12-07 23:11  maplefighting  阅读(484)  评论(0编辑  收藏  举报