Concurrent Programming in Java 的做者 Doug Lea 编写了一个极其优秀的、免费的并发实用程序包,它包括并发应用程序的锁、互斥、队列、线程池、轻量级任务、有效的并发集合、原子的算术操做和其它基本构件。咱们通常称这个包为 J.U.C。java
1. JUC概况
如下是Java JUC包的主体结构:
node
- Atomic : AtomicInteger
- Locks : Lock, Condition, ReadWriteLock
- Collections : Queue, ConcurrentMap
- Executer : Future, Callable, Executor
- Tools : CountDownLatch, CyclicBarrier, Semaphore
2. 原子操做
多个线程执行一个操做时,其中任何一个线程要么彻底执行完此操做,要么没有执行此操做的任何步骤,那么这个操做就是原子的。出现缘由: synchronized的代价比较高。算法
如下以AtomicInteger为例:缓存
- int addAndGet(int delta):以原子方式将给定值与当前值相加。 实际上就是等于线程安全版本的i =i+delta操做。
- boolean compareAndSet(int expect, int update):若是当前值 == 预期值,则以原子方式将该值设置为给定的更新值。 若是成功就返回true,不然返回false,而且不修改原值。
- int decrementAndGet():以原子方式将当前值减 1。 至关于线程安全版本的–i操做。
- int getAndAdd(int delta):以原子方式将给定值与当前值相加。 至关于线程安全版本的t=i;i+=delta;return t;操做。
- int getAndDecrement():以原子方式将当前值减 1。 至关于线程安全版本的i–操做。
- int getAndIncrement():以原子方式将当前值加 1。 至关于线程安全版本的i++操做。
- int getAndSet(int newValue):以原子方式设置为给定值,并返回旧值。 至关于线程安全版本的t=i;i=newValue;return t;操做。
- int incrementAndGet():以原子方式将当前值加 1。 至关于线程安全版本的++i操做。
3. 指令重排
你的程序并不能老是保证符合CPU处理的特性。安全
要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。并发

多核CPU,大压力下,两个线程交替执行,x,y输出结果不肯定。可能结果:app
1ide 2函数 3高并发 4 |
x = 0 , y = 1 x = 1 , y = 1 x = 1 , y = 0 x = 0 , y = 0 |
4. Happens-before法则:(Java 内存模型)
若是动做B要看到动做A的执行结果(不管A/B是否在同一个线程里面执行),那么A/B就须要知足happens-before关系。
Happens-before的几个规则:
- Program order rule:同一个线程中的每一个Action都happens-before于出如今其后的任何一个Action。
- Monitor lock rule:对一个监视器的解锁happens-before于每个后续对同一个监视器的加锁。
- Volatile variable rule:对volatile字段的写入操做happens-before于每个后续的同一个字段的读操做。
- Thread start rule:Thread.start()的调用会happens-before于启动线程里面的动做。
- Thread termination rule:Thread中的全部动做都happens-before于其余线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false。
- Interruption rule:一个线程A调用另外一个另外一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检测到B的isInterrupted()或者interrupted())。
- Finalizer rule:一个对象构造函数的结束happens-before与该对象的finalizer的开始
- Transitivity:若是A动做happens-before于B动做,而B动做happens-before与C动做,那么A动做happens-before于C动做。
由于CPU是能够不按咱们写代码的顺序执行内存的存取过程的,也就是指令会乱序或并行运行, 只有上面的happens-before所规定的状况下,才保证顺序性。
JMM的特性:
多个CPU之间的缓存也不保证明时同步;
JMM不保证建立过程的原子性,读写并发时,可能看到不完整的对象。(so D-check)
volatile语义:
volatile实现了相似synchronized的语义,却又没有锁机制。它确保对 volatile字段的更新以可预见的方式告知其余的线程。
- Java 存储模型不会对volatile指令的操做进行重排序:这个保证对volatile变量的操做时按照指令的出现顺序执行的。
- volatile变量不会被缓存在寄存器中(只有拥有线程可见),每次老是从主存中读取volatile变量的结果。
ps:volatile并不能保证线程安全的,也就是说volatile字段的操做不是原子性的,volatile变量只能保证可见性。
5. CAS操做
Compare and Swap
CAS有3个操做数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改成B,不然什么都不作。
实现简单的非阻塞算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private volatile int value; // 借助volatile原语,保证线程间的数据是可见的 public final int get() { return value; } public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1 ; if (compareAndSet(current, next)) return next; } //Spin自旋等待直到返为止置 } |
整个J.U.C都是创建在CAS之上的,对于synchronized阻塞算法,J.U.C在性能上有了很大的提高。会出现所谓的“ABA”问题
6. Lock 锁
Synchronized属于独占锁,高并发时性能不高,JDK5之后开始用JNI实现更高效的锁操做。
Lock—->
ReentrantLock—->
ReentrantReadWriteLock.ReadLock / ReentrantReadWriteLock.writeLock
ReadWriteLock—-> ReentrantReadWriteLock
LockSupport
Condition
方法名称 |
做用 |
void lock() |
获取锁。若是锁不可用,出于线程调度目的,将禁用当前线程,而且在得到锁以前,该线程将一直处于休眠状态。 |
void lockInterruptibly() throws InterruptedException; |
若是当前线程未被中断,则获取锁。若是锁可用,则获取锁,并当即返回。 |
Condition newCondition(); |
返回绑定到此 Lock 实例的新 Condition 实例 |
boolean tryLock(); |
仅在调用时锁为空闲状态才获取该锁 |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; |
若是锁在给定的等待时间内空闲,而且当前线程未被中断,则获取锁 |
void unlock(); |
释放锁 |
PS : 通常来讲,获取锁和释放锁是成对儿的操做,这样能够避免死锁和资源的浪费。
7. AQS
锁机制实现的核心所在。AbstractQueuedSynchronizer是Lock/Executor实现的前提。

AQS实现:
基本的思想是表现为一个同步器,AQS支持下面两个操做:
acquire:
1 2 3 4 5 |
while (synchronization state does not allow acquire){ enqueue current thread if not already queued; possibly block current thread; } dequeue current thread if it was queued; |
release:
1 2 3 |
update synchronization state; if (state may permit a blocked thread to acquire) unlock one or more queued threads; |
要支持这两个操做,须要实现的三个条件:
- Atomically managing synchronization state(原子性操做同步器的状态位)
- Blocking and unblocking threads(阻塞和唤醒线程)
- Maintaining queues(维护一个有序的队列)
Atomically managing synchronization state
使用一个32位整数来描述状态位:private volatile int state; 对其进行CAS操做,确保值的正确性。
Blocking and unblocking threads
JDK 5.0之后利用JNI在LockSupport类中实现了线程的阻塞和唤醒。
LockSupport.park() //在当前线程中调用,致使线程阻塞
LockSupport.park(Object)
LockSupport.unpark(Thread)
Maintaining queues
在AQS中采用CHL列表来解决有序的队列的问题。(CHL= Craig, Landin, and Hagersten)

Node里面是什么结构?

WaitStatus –>节点的等待状态,一个节点可能位于如下几种状态:
- CANCELLED = 1: 节点操做由于超时或者对应的线程被interrupt。节点不该该不留在此状态,一旦达到此状态将从CHL队列中踢出。
- SIGNAL = -1: 节点的继任节点是(或者将要成为)BLOCKED状态(例如经过LockSupport.park()操做),所以一个节点一旦被释放(解锁)或者取消就须要唤醒(LockSupport.unpack())它的继任节点。
- CONDITION = -2:代表节点对应的线程由于不知足一个条件(Condition)而被阻塞。
- 0: 正常状态,新生的非CONDITION节点都是此状态。
非负值标识节点不须要被通知(唤醒)。
队列管理操做:
入队enqueue:
采用CAS操做,每次比较尾结点是否一致,而后插入的到尾结点中。
1 2 3 |
do { pred = tail; } while ( !compareAndSet(pred,tail,node) ); |
出队dequeue:
1 2 |
while (pred.status != RELEASED) ; head = node; |

加锁操做:
1 2 3 4 5 |
public final void acquire( int arg) { if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } |
释放操做:
1 2 3 4 5 6 7 8 9 |
public final boolean release( int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0 ) unparkSuccessor(h); return true ; } return false ; } |
The synchronizer framework provides a ConditionObject class for use by synchronizers that maintain exclusivesynchronization and conform to the Lock interface. —— Doug Lea《 The java.util.concurrent Synchronizer Framework 》
如下是AQS队列和Condition队列的出入结点的示意图,能够经过这几张图看出线程结点在两个队列中的出入关系和条件。


