Java线程同步之一--AQS
添加时间:2013-7-5 点击量:
 
    
 
    线程同步是指两个并发履行的线程在同一时候不应时履行某一项目组的法度。同步题目在生活生计中也很常见,就比如在麦当劳点餐,假设只有一个办事员可以或许供给点餐办事。每个办事员在同一时刻只能接待一个顾客的点餐,那么除了正在接待的顾客,其他人只能守候列队。当一个点餐办事完成之后,其他顾客就可以上去进行点餐。
 
从这个例子中可以看到如下几个存眷点:
- 点餐办事为临界区域(critical area),其可同时进行的数量,即为有几许人可进入临界区域。
- 列队即为对今朝临时无法取得点餐办事的人的一种处理惩罚体式格式。这种处理惩罚体式格式的特点有公允性(按次序),效力性(接办快为好)等。
- 顾客进行列队和从部队中叫一个顾客来进行办事即为睡眠(park)和唤醒(unpark)机制。
 
      并发中线程同步是重点需存眷的题目,线程同步天然也有必然的模式,DougLea就写出了一个简单的框架AQS用来支撑一大类线程同步对象,如ReentrantLock,CountdownLatch,Semphaore等。
      AQS是concurrent包中的一系列同步对象的根蒂根基实现,其供给了状况位,线程梗阻-唤醒办法,CAS操纵。基起原根蒂根基理就是按照状况位来把握线程的入队梗阻、出队唤醒来解决同步题目。
 
入队:
 
出队:
      
 
 
二、代码解析
     下面以ReentrantLock来申明AQS的构成构件的工作景象:
     在ReentrantLock中封装了一个同步器Sync,持续了AbstractQueuedSynchronizer,按照对临界区的接见的公允性请求不合,又分为NonfairSync和FairSync。为了简化起见,就取最简单的NonFairSync作为例子来申明:
      1. 对于临界区的把握:
     
      java.util.concurrent.locks.ReentrantLock.NonfairSync
1: final void lock() {
2: 
3: if (compareAndSetState(0, 1))
4: 
5: setExclusiveOwnerThread(Thread.currentThread());
6: 
7: else
8: 
9: acquire(1);
10: 
11: }
12: 
          从以上代码可以看出,其首要目标是采取cas斗劲临界区的状况。
 
         1.1. 若是为0,将其设置为1,并记录当火线程(当火线程可进入临界区);
         1.2. 若是为1,测验测验获取临界区把握
         java.util.concurrent.locks.AbstractQueuedSynchronizer
1: public final void acquire(int arg) {
2: 
3: if (!tryAcquire(arg) &&
4: 
5: acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
6: 
7: selfInterrupt();
8: 
9: }
10: 
               1.2.1. NonFairLock的tryAcquire实现为:
1: final boolean nonfairTryAcquire(int acquires) {
2: 
3: final Thread current = Thread.currentThread();
4: 
5: int c = getState();
6: 
7: if (c == 0) {
8: 
9: if (compareAndSetState(0, acquires)) {
10: 
11: setExclusiveOwnerThread(current);
12: 
13: return true;
14: 
15: }
16: 
17: }
18: 
19: else if (current == getExclusiveOwnerThread()) {
20: 
21: int nextc = c + acquires;
22: 
23: if (nextc < 0) // overflow
24:&#160;
25: throw new Error("Maximum lock count exceeded");
26:&#160;
27: setState(nextc);
28:&#160;
29: return true;
30:&#160;
31: }
32:&#160;
33: return false;
34:&#160;
35: }
36:&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160; 上述代如果针对大项目组线程进入临界区工作时候不会很长而进行的机能优化,第一次测验测验失败了,极有可能过一会儿锁就开释了,是以从头去测验测验获取锁。
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 1.2.2. 以下这段代码是锁的精华项目组
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; java.util.concurrent.locks.AbstractQueuedSynchronizer
1: final boolean acquireQueued(final Node node, int arg) {
2:&#160;
3: try {
4:&#160;
5: boolean interrupted = false;
6:&#160;
7: for (;;) {
8:&#160;
9: final Node p = node.predecessor();
10:&#160;
11: if (p == head && tryAcquire(arg)) {
12:&#160;
13: setHead(node);
14:&#160;
15: p.next = null; // help GC
16:&#160;
17: return interrupted;
18:&#160;
19: }
20:&#160;
21: if (shouldParkAfterFailedAcquire(p, node) &&
22:&#160;
23: parkAndCheckInterrupt())
24:&#160;
25: interrupted = true;
26:&#160;
27: }
28:&#160;
29: } catch (RuntimeException ex) {
30:&#160;
31: cancelAcquire(node);
32:&#160;
33: throw ex;
34:&#160;
35: }
36:&#160;
37: }
38:&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160; 在无穷轮回中完成了对线程的梗阻和唤醒。梗阻在parkAndCheckInterrupt()唤醒后从此处进行开释。
算法过程:
- 从参加队列的node开端反向查找,将前一个元素赋值给p;
- 若是p是head,那么试着再获得一次锁tryAcquire(arg),成功则将head指针往后移动,并跳出轮回;
- 若是上一步调测验测验失败,那么进行测试是否要park ,若是状况为0,将其标识表记标帜为SIGNAL,并返回false;
- 再反复搜检一次,发明其头部的waitStatus为-1.Node.signal。确认须要park successor; 进行parkAndCheckInterrupt()将当火线程梗阻。
&#160;
2. 对于临界区的开释
2.1. java.util.concurrent.locks.AbstractQueuedSynchronizer
1: public final boolean release(int arg) {
2:&#160;
3: if (tryRelease(arg)) {
4:&#160;
5: Node h = head;
6:&#160;
7: if (h != null && h.waitStatus != 0)
8:&#160;
9: unparkSuccessor(h);
10:&#160;
11: return true;
12:&#160;
13: }
14:&#160;
15: return false;
16:&#160;
17: }
18:&#160;
2.1.1. java.util.concurrent.locks.ReentrantLock.Sync
1: protected final boolean tryRelease(int releases) {
2:&#160;
3: int c = getState() - releases;
4:&#160;
5: if (Thread.currentThread() != getExclusiveOwnerThread())
6:&#160;
7: throw new IllegalMonitorStateException();
8:&#160;
9: boolean free = false;
10:&#160;
11: if (c == 0) {
12:&#160;
13: free = true;
14:&#160;
15: setExclusiveOwnerThread(null);
16:&#160;
17: }
18:&#160;
19: setState(c);
20:&#160;
21: return free;
22:&#160;
23: }
24:&#160;
&#160;&#160; &#160;&#160;&#160; 将state进行变更-releases,搜检当火线程是否是拿住锁的线程,不然掷出异常.若是为0,将持有锁线程标识表记标帜为null。
&#160;
&#160;&#160;&#160; 从ReentrantLock例子可以看出AQS的工作道理,更为精妙的是,在这几个根蒂根基机建造用下衍生了很多种并发对象,今后的介绍中可以看到。
我俩之间有着强烈的吸引力。短短几个小时后,我俩已经明白:我们的心是一个整体的两半,我俩的心灵是孪生兄妹,是知己。她让我感到更有活力,更完美,更幸福。即使她不在我身边,我依然还是感到幸福,因为她总是以这样或者那样的方式出现在我心头。——恩里克·巴里奥斯《爱的文明》
&#160;
&#160;&#160;&#160;&#160;
&#160;
&#160;&#160;&#160; 线程同步是指两个并发履行的线程在同一时候不应时履行某一项目组的法度。同步题目在生活生计中也很常见,就比如在麦当劳点餐,假设只有一个办事员可以或许供给点餐办事。每个办事员在同一时刻只能接待一个顾客的点餐,那么除了正在接待的顾客,其他人只能守候列队。当一个点餐办事完成之后,其他顾客就可以上去进行点餐。
&#160;
从这个例子中可以看到如下几个存眷点:
- 点餐办事为临界区域(critical area),其可同时进行的数量,即为有几许人可进入临界区域。
- 列队即为对今朝临时无法取得点餐办事的人的一种处理惩罚体式格式。这种处理惩罚体式格式的特点有公允性(按次序),效力性(接办快为好)等。
- 顾客进行列队和从部队中叫一个顾客来进行办事即为睡眠(park)和唤醒(unpark)机制。
&#160;
&#160;&#160;&#160;&#160;&#160; 并发中线程同步是重点需存眷的题目,线程同步天然也有必然的模式,DougLea就写出了一个简单的框架AQS用来支撑一大类线程同步对象,如ReentrantLock,CountdownLatch,Semphaore等。
&#160;&#160;&#160;&#160;&#160; AQS是concurrent包中的一系列同步对象的根蒂根基实现,其供给了状况位,线程梗阻-唤醒办法,CAS操纵。基起原根蒂根基理就是按照状况位来把握线程的入队梗阻、出队唤醒来解决同步题目。
&#160;
入队:
&#160;
出队:
&#160;&#160;&#160;&#160;&#160;&#160;
&#160;
&#160;
二、代码解析
&#160;&#160;&#160;&#160; 下面以ReentrantLock来申明AQS的构成构件的工作景象:
&#160;&#160;&#160;&#160; 在ReentrantLock中封装了一个同步器Sync,持续了AbstractQueuedSynchronizer,按照对临界区的接见的公允性请求不合,又分为NonfairSync和FairSync。为了简化起见,就取最简单的NonFairSync作为例子来申明:
&#160;&#160;&#160;&#160;&#160; 1. 对于临界区的把握:
&#160;&#160;&#160;&#160;&#160;
&#160;&#160;&#160;&#160;&#160; java.util.concurrent.locks.ReentrantLock.NonfairSync
1: final void lock() {
2:&#160;
3: if (compareAndSetState(0, 1))
4:&#160;
5: setExclusiveOwnerThread(Thread.currentThread());
6:&#160;
7: else
8:&#160;
9: acquire(1);
10:&#160;
11: }
12:&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 从以上代码可以看出,其首要目标是采取cas斗劲临界区的状况。
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 1.1. 若是为0,将其设置为1,并记录当火线程(当火线程可进入临界区);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 1.2. 若是为1,测验测验获取临界区把握
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; java.util.concurrent.locks.AbstractQueuedSynchronizer
1: public final void acquire(int arg) {
2:&#160;
3: if (!tryAcquire(arg) &&
4:&#160;
5: acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
6:&#160;
7: selfInterrupt();
8:&#160;
9: }
10:&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 1.2.1. NonFairLock的tryAcquire实现为:
1: final boolean nonfairTryAcquire(int acquires) {
2:&#160;
3: final Thread current = Thread.currentThread();
4:&#160;
5: int c = getState();
6:&#160;
7: if (c == 0) {
8:&#160;
9: if (compareAndSetState(0, acquires)) {
10:&#160;
11: setExclusiveOwnerThread(current);
12:&#160;
13: return true;
14:&#160;
15: }
16:&#160;
17: }
18:&#160;
19: else if (current == getExclusiveOwnerThread()) {
20:&#160;
21: int nextc = c + acquires;
22:&#160;
23: if (nextc < 0) // overflow
24:&#160;
25: throw new Error("Maximum lock count exceeded");
26:&#160;
27: setState(nextc);
28:&#160;
29: return true;
30:&#160;
31: }
32:&#160;
33: return false;
34:&#160;
35: }
36:&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160; 上述代如果针对大项目组线程进入临界区工作时候不会很长而进行的机能优化,第一次测验测验失败了,极有可能过一会儿锁就开释了,是以从头去测验测验获取锁。
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 1.2.2. 以下这段代码是锁的精华项目组
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; java.util.concurrent.locks.AbstractQueuedSynchronizer
1: final boolean acquireQueued(final Node node, int arg) {
2:&#160;
3: try {
4:&#160;
5: boolean interrupted = false;
6:&#160;
7: for (;;) {
8:&#160;
9: final Node p = node.predecessor();
10:&#160;
11: if (p == head && tryAcquire(arg)) {
12:&#160;
13: setHead(node);
14:&#160;
15: p.next = null; // help GC
16:&#160;
17: return interrupted;
18:&#160;
19: }
20:&#160;
21: if (shouldParkAfterFailedAcquire(p, node) &&
22:&#160;
23: parkAndCheckInterrupt())
24:&#160;
25: interrupted = true;
26:&#160;
27: }
28:&#160;
29: } catch (RuntimeException ex) {
30:&#160;
31: cancelAcquire(node);
32:&#160;
33: throw ex;
34:&#160;
35: }
36:&#160;
37: }
38:&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160; 在无穷轮回中完成了对线程的梗阻和唤醒。梗阻在parkAndCheckInterrupt()唤醒后从此处进行开释。
算法过程:
- 从参加队列的node开端反向查找,将前一个元素赋值给p;
- 若是p是head,那么试着再获得一次锁tryAcquire(arg),成功则将head指针往后移动,并跳出轮回;
- 若是上一步调测验测验失败,那么进行测试是否要park ,若是状况为0,将其标识表记标帜为SIGNAL,并返回false;
- 再反复搜检一次,发明其头部的waitStatus为-1.Node.signal。确认须要park successor; 进行parkAndCheckInterrupt()将当火线程梗阻。
&#160;
2. 对于临界区的开释
2.1. java.util.concurrent.locks.AbstractQueuedSynchronizer
1: public final boolean release(int arg) {
2:&#160;
3: if (tryRelease(arg)) {
4:&#160;
5: Node h = head;
6:&#160;
7: if (h != null && h.waitStatus != 0)
8:&#160;
9: unparkSuccessor(h);
10:&#160;
11: return true;
12:&#160;
13: }
14:&#160;
15: return false;
16:&#160;
17: }
18:&#160;
2.1.1. java.util.concurrent.locks.ReentrantLock.Sync
1: protected final boolean tryRelease(int releases) {
2:&#160;
3: int c = getState() - releases;
4:&#160;
5: if (Thread.currentThread() != getExclusiveOwnerThread())
6:&#160;
7: throw new IllegalMonitorStateException();
8:&#160;
9: boolean free = false;
10:&#160;
11: if (c == 0) {
12:&#160;
13: free = true;
14:&#160;
15: setExclusiveOwnerThread(null);
16:&#160;
17: }
18:&#160;
19: setState(c);
20:&#160;
21: return free;
22:&#160;
23: }
24:&#160;
&#160;&#160;&#160; 将state进行变更-releases,搜检当火线程是否是拿住锁的线程,不然掷出异常.若是为0,将持有锁线程标识表记标帜为null。
&#160;
&#160;&#160;&#160; 从ReentrantLock例子可以看出AQS的工作道理,更为精妙的是,在这几个根蒂根基机建造用下衍生了很多种并发对象,今后的介绍中可以看到。
我俩之间有着强烈的吸引力。短短几个小时后,我俩已经明白:我们的心是一个整体的两半,我俩的心灵是孪生兄妹,是知己。她让我感到更有活力,更完美,更幸福。即使她不在我身边,我依然还是感到幸福,因为她总是以这样或者那样的方式出现在我心头。——恩里克·巴里奥斯《爱的文明》