目录[-]
以下是Java JUC包的主体结构:
多个线程执行一个操作时,其中任何一个线程要么完全执行完此操作,要么没有执行此操作的任何步骤,那么这个操作就是原子的。出现原因: synchronized的代价比较高。
以下以AtomicInteger为例:
你的程序并不能总是保证符合CPU处理的特性。
要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。
多核CPU,大压力下,两个线程交替执行,x,y输出结果不确定。可能结果:
1
2
3
4
|
x =0, y =1
x =1, y =1
x =1, y =0
x =0, y =0
|
如果动作B要看到动作A的执行结果(无论A/B是否在同一个线程里面执行),那么A/B就需要满足happens-before关系。
Happens-before的几个规则:
多个CPU之间的缓存也不保证实时同步;
JMM不保证创建过程的原子性,读写并发时,可能看到不完整的对象。(so D-check)
volatile实现了类似synchronized的语义,却又没有锁机制。它确保对 volatile字段的更新以可预见的方式告知其他的线程。
ps:volatile并不能保证线程安全的,也就是说volatile字段的操作不是原子性的,volatile变量只能保证可见性。
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
|
privatevolatileintvalue;// 借助volatile原语,保证线程间的数据是可见的
publicfinalintget() {
returnvalue;
}
publicfinalintincrementAndGet() {
for(;;) {
intcurrent = get();
intnext = current +1;
if(compareAndSet(current, next))
returnnext;
}//Spin自旋等待直到返为止置
}
|
整个J.U.C都是建立在CAS之上的,对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。会出现所谓的“ABA”问题
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 : 一般来说,获取锁和释放锁是成对儿的操作,这样可以避免死锁和资源的浪费。
注:在 finally 里面做释放锁的操作
锁机制实现的核心所在。AbstractQueuedSynchronizer是Lock/Executor实现的前提。
基本的思想是表现为一个同步器,AQS支持下面两个操作:
acquire:
1
2
3
4
5
|
while(synchronization state does not allow acquire){
enqueue current threadifnot already queued;
possibly block current thread;
}
dequeue current threadifit was queued;
|
release:
1
2
3
|
update synchronization state;
if(state may permit a blocked thread to acquire)
unlock one or more queued threads;
|
要支持这两个操作,需要实现的三个条件:
使用一个32位整数来描述状态位:private volatile int state; 对其进行CAS操作,确保值的正确性。
JDK 5.0以后利用JNI在LockSupport类中实现了线程的阻塞和唤醒。
LockSupport.park() //在当前线程中调用,导致线程阻塞
LockSupport.park(Object)
LockSupport.unpark(Thread)
在AQS中采用CHL列表来解决有序的队列的问题。(CHL= Craig, Landin, and Hagersten)
Node里面是什么结构?
WaitStatus –>节点的等待状态,一个节点可能位于以下几种状态:
非负值标识节点不需要被通知(唤醒)。
队列管理操作:
入队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(intarg) {
if(!tryAcquire(arg))
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
|
释放操作:
1
2
3
4
5
6
7
8
9
|
public final boolean release(intarg) {
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队列的出入结点的示意图,可以通过这几张图看出线程结点在两个队列中的出入关系和条件。
以下是Java JUC包的主体结构:
多个线程执行一个操作时,其中任何一个线程要么完全执行完此操作,要么没有执行此操作的任何步骤,那么这个操作就是原子的。出现原因: synchronized的代价比较高。
以下以AtomicInteger为例:
你的程序并不能总是保证符合CPU处理的特性。
要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。
多核CPU,大压力下,两个线程交替执行,x,y输出结果不确定。可能结果:
1
2
3
4
|
x =0, y =1
x =1, y =1
x =1, y =0
x =0, y =0
|
如果动作B要看到动作A的执行结果(无论A/B是否在同一个线程里面执行),那么A/B就需要满足happens-before关系。
Happens-before的几个规则:
多个CPU之间的缓存也不保证实时同步;
JMM不保证创建过程的原子性,读写并发时,可能看到不完整的对象。(so D-check)
volatile实现了类似synchronized的语义,却又没有锁机制。它确保对 volatile字段的更新以可预见的方式告知其他的线程。
ps:volatile并不能保证线程安全的,也就是说volatile字段的操作不是原子性的,volatile变量只能保证可见性。
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
|
privatevolatileintvalue;// 借助volatile原语,保证线程间的数据是可见的
publicfinalintget() {
returnvalue;
}
publicfinalintincrementAndGet() {
for(;;) {
intcurrent = get();
intnext = current +1;
if(compareAndSet(current, next))
returnnext;
}//Spin自旋等待直到返为止置
}
|
整个J.U.C都是建立在CAS之上的,对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。会出现所谓的“ABA”问题
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 : 一般来说,获取锁和释放锁是成对儿的操作,这样可以避免死锁和资源的浪费。
注:在 finally 里面做释放锁的操作
锁机制实现的核心所在。AbstractQueuedSynchronizer是Lock/Executor实现的前提。
基本的思想是表现为一个同步器,AQS支持下面两个操作:
acquire:
1
2
3
4
5
|
while(synchronization state does not allow acquire){
enqueue current threadifnot already queued;
possibly block current thread;
}
dequeue current threadifit was queued;
|
release:
1
2
3
|
update synchronization state;
if(state may permit a blocked thread to acquire)
unlock one or more queued threads;
|
要支持这两个操作,需要实现的三个条件:
使用一个32位整数来描述状态位:private volatile int state; 对其进行CAS操作,确保值的正确性。
JDK 5.0以后利用JNI在LockSupport类中实现了线程的阻塞和唤醒。
LockSupport.park() //在当前线程中调用,导致线程阻塞
LockSupport.park(Object)
LockSupport.unpark(Thread)
在AQS中采用CHL列表来解决有序的队列的问题。(CHL= Craig, Landin, and Hagersten)
Node里面是什么结构?
WaitStatus –>节点的等待状态,一个节点可能位于以下几种状态:
非负值标识节点不需要被通知(唤醒)。
队列管理操作:
入队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(intarg) {
if(!tryAcquire(arg))
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
|
释放操作:
1
2
3
4
5
6
7
8
9
|
public final boolean release(intarg) {
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队列的出入结点的示意图,可以通过这几张图看出线程结点在两个队列中的出入关系和条件。