# Java-Concurrency-study **Repository Path**: hsogoo/Java-Concurrency-study ## Basic Information - **Project Name**: Java-Concurrency-study - **Description**: Java-Concurrency学习笔记 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2018-08-27 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Java-Concurrency-study #### 学习内容 java.util.concurrent(简称JUC) #### 指令重排序 Java语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。这个过程通过叫做指令的重排序。指令重排序存在的意义在于:JVM能够根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器的性能。 程序执行最简单的模型是按照指令出现的顺序执行,这样就与执行指令的CPU无关,最大限度的保证了指令的可移植性。这个模型的专业术语叫做顺序化一致性模型。但是现代计算机体系和处理器架构都不保证这一点(因为人为的指定并不能总是保证符合CPU处理的特性)。 #### Happens-before法则 Java存储模型有一个happens-before原则,就是如果动作B要看到动作A的执行结果(无论A/B是否在同一个线程里面执行),那么A/B就需要满足happens-before关系。 在介绍happens-before法则之前介绍一个概念:JMM动作(Java Memeory Model Action),Java存储模型动作。一个动作(Action)包括:变量的读写、监视器加锁和释放锁、线程的start()和join()。后面还会提到锁的的。 happens-before完整规则: - (1)同一个线程中的每个Action都happens-before于出现在其后的任何一个Action。 - (2)对一个监视器的解锁happens-before于每一个后续对同一个监视器的加锁。 - (3)对volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作。 - (4)Thread.start()的调用会happens-before于启动线程里面的动作。 - (5)Thread中的所有动作都happens-before于其他线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false。 - (6)一个线程A调用另一个另一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检测到B的isInterrupted()或者interrupted())。 - (7)一个对象构造函数的结束happens-before与该对象的finalizer的开始 - (8)如果A动作happens-before于B动作,而B动作happens-before与C动作,那么A动作happens-before于C动作。 #### volatile语义 到目前为止,我们多次提到volatile,但是却仍然没有理解volatile的语义。 volatile相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其他的线程。 volatile包含以下语义: (1)Java 存储模型不会对volatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。 (2)volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其他对CPU不可见的地方,每次总是从主存中读取volatile变量的结果。也就是说对于volatile变量的修改,其它线程总是可见的,并且不是使用自己线程栈内部的变量。也就是在happens-before法则中,对一个valatile变量的写操作后,其后的任何读操作理解可见此写操作的结果。 尽管volatile变量的特性不错,但是volatile并不能保证线程安全的,也就是说volatile字段的操作不是原子性的,volatile变量只能保证可见性(一个线程修改后其它线程能够理解看到此变化后的结果),要想保证原子性,目前为止只能加锁! #### CAS 操作 乐观锁用到的机制就是CAS,Compare and Swap乐观锁用到的机制就是CAS,Compare and Swap。利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。**CAS看起来很爽,但是会导致“ABA问题”**。 #### 锁机制 尽管synchronized在语法上已经足够简单了,在JDK 5之前只能借助此实现,但是由于是独占锁,性能却不高,因此JDK 5以后就开始借助于JNI来完成更高级的锁实现。 JDK 5中的锁是接口java.util.concurrent.locks.Lock。另外java.util.concurrent.locks.ReadWriteLock提供了一对可供读写并发的锁。 ``` public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); } ``` #### AbstractQueuedSynchronizer(AQS) ASQ将线程封装到一个Node里面,并维护一个CHL Node FIFO队列,它是一个非阻塞的FIFO队列,也就是说在并发条件下往此队列做插入或移除操作不会阻塞,是通过自旋锁和CAS保证节点插入和移除的原子性,实现无锁快速插入。 其实AbstractQueuedSynchronizer主要就是维护了一个state属性、一个FIFO队列和线程的阻塞与解除阻塞操作。state表示同步状态,它的类型为32位整型,对state的更新必须要保证原子性。这里的队列是一个双向链表,每个节点里面都有一个prev和next,它们分别是前一个节点和后一个节点的引用。需要注意的是此双向链表除了链头其他每个节点内部都包含一个线程,而链头可以理解为一个空节点。 ![输入图片说明](https://images.gitee.com/uploads/images/2018/0910/160153_e2aed66d_1421983.png "AQS (1).png") 其中CHL Node FIFO队列的结构如下: ![输入图片说明](https://images.gitee.com/uploads/images/2018/0910/152846_999a42fd_1421983.png "CHL队列 (1).png") #### Condition Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。 Condition的实现类ConditionObject #### 线程中断 Thread.interrupt()线程中断线程状态的变化 NEW & TERMINATED,interrupt()方法调用后,isInterrupted()返回false, RUNNABLE & BLOCKED,interrupt()方法调用后,isInterrupted()返回true,但是仅仅是标记了中断标识位,线程不会中断, 可以在interrupt()方法调用后,判断isInterrupted()来退出线程 WAITING & TIMED_WAITING,interrupt()方法调用后,捕获了InterruptedException异常,该线程的中断标志位被清空 请参考ThreadInterruptTest #### 总结 volatile加unsafe的cas操作加CHL队列加LockSupport的park和unpark