jdk-AbstractQueuedSynchronizer(四)
来源:互联网 发布:qq软件下载安装 编辑:程序博客网 时间:2024/06/04 19:33
第四篇来看看什么呢?来看看写第一篇时就想看的Condition 队列。AQS中维护了两个队列,一个sys等待队列,这个队列是那些想要去获取锁的线程获取不到而进入的一个获取锁的队列,另一个就是Condition条件等待队列。
本篇结合是ReentrantLock 去分析这个Condition条件队列。
关于ReentrantLock和Condition结合使用,主要调用的代码是awqit和signal两个方法。
public final void await() throws InterruptedException { if (Thread.interrupted()) //线程被打断的话直接抛出异常 throw new InterruptedException(); //这边就是加入Condition队列 Node node = addConditionWaiter(); //state的获取
//release掉当前节点,释放锁 int savedState = fullyRelease(node); int interruptMode = 0; //检查是否在锁队列中,不在的话进入循环,进入循环就是将自己睡着了 // 在的话直接跳过循环,在的话说明当前线程有资格去竞争锁了。 while (!isOnSyncQueue(node)) { LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } //acquireQueued就是之前说的加入锁队列。 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
private Node addConditionWaiter() { Node t = lastWaiter; // If lastWaiter is cancelled, clean out. if (t != null && t.waitStatus != Node.CONDITION) { //注释很清楚,清除状态是cancelled掉的节点。 unlinkCancelledWaiters(); t = lastWaiter; } //构造出一个当前线程的节点,标志状态state为condition Node node = new Node(Thread.currentThread(), Node.CONDITION); if (t == null) firstWaiter = node; else t.nextWaiter = node; lastWaiter = node; //将第一个节点连接到node节点处 return node; }
获取状态时顺便release掉当前节点,release时会唤醒锁队列中下一个为Signal状态的节点,之前分析过。
final int fullyRelease(Node node) { boolean failed = true; try { int savedState = getState(); if (release(savedState)) { failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) node.waitStatus = Node.CANCELLED; } }
final boolean isOnSyncQueue(Node node) { //如果节点状态是-2了,说明不在了 if (node.waitStatus == Node.CONDITION || node.prev == null) return false; if (node.next != null) // If has successor, it must be on queue return true; /* * node.prev can be non-null, but not yet on queue because * the CAS to place it on queue can fail. So we have to * traverse from tail to make sure it actually made it. It * will always be near the tail in calls to this method, and * unless the CAS failed (which is unlikely), it will be * there, so we hardly ever traverse much. */ //最终从尾部节点一直找,确保不在锁队列中 return findNodeFromTail(node); }
Thread1现在加入了等待队列中,而锁的队列里唤醒了下一个线程Thread 2,正在执行。那么当天执行signal的时候呢,就是发出一个信号了。
现在来看signal操作。
public final void signal() { if (!isHeldExclusively()) //判断是否是当前线程再执行signal操作 throw new IllegalMonitorStateException(); //获取头节点。 Node first = firstWaiter; if (first != null) doSignal(first); }
private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); //在本例中,首次循环即进行状态设置,返回true,循环一次就退出while,do的操作就是将当前Condition中的节点给剔除掉,移动到锁队列中 } final boolean transferForSignal(Node node) { /* * If cannot change waitStatus, the node has been cancelled. */ //本例中第一个节点的状态为-2,修改为0,跳过。 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; /* * Splice onto queue and try to set waitStatus of predecessor to * indicate that thread is (probably) waiting. If cancelled or * attempt to set waitStatus fails, wake up to resync (in which * case the waitStatus can be transiently and harmlessly wrong). */ //此处就是将Condition队列中的节点加入到锁队列中, Node p = enq(node); int ws = p.waitStatus; //一般情况下,这个队列中前驱节点的状态都是signal的,所以这一步一般不执行, //执行的话也是直接进行直接唤醒操作。 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; }
那么至此,Thread1又进入了锁队列中,等待获取锁。
总结一下。
1、首先,线程1调用lock.lock()时,由于此时锁并没有被其它线程占用,因此线程1直接获得锁并不会进入AQS同步队列中进行等待。
2、在线程1执行期间,线程2调用lock.lock()时由于锁已经被线程1占用,因此,线程2进入AQS同步队列中进行等待。
3、在线程1中执行condition.await()方法后,线程1释放锁并进入条件队列Condition中等待signal信号的到来。
4、线程2,因为线程1释放锁的关系,会唤醒AQS队列中的线程2,所以线程2会获取到锁。
5、线程2调用signal方法,这个时候Condition的等待队列中只有线程1一个节点,于是它被取出来,并被加入到AQS的等待队列中。注意,这个时候,线程1 并没有被唤醒。只是加入到了AQS等待队列中去了
6、待线程2执行完成之后并调用lock.unlock()释放锁之后,会唤醒此时在AQS队列中的头结点.所以线程1开始争夺锁(由于此时只有线程1在AQS队列中,因此没人与其争夺),如果获得锁继续执行。
简单的生产者消费者代码
public class Depot { private ReentrantLock lock = new ReentrantLock(); /*生产者增加*/ private Condition add = lock.newCondition(); /*消费者消费*/ private Condition remove = lock.newCondition(); /*null说明仓库中没有值*/ private Double num = null; public void add(){ lock.lock(); try{ while (num != null){ //num不为null说明生产者已经生产了数据在其中,需要等待消费者消费之后再次唤醒生成数据 add.await(); } num = Math.random(); System.out.println(Thread.currentThread()+" 生产出一个货物 "+ num); //唤醒消费者,来消费啦 remove.signal(); }catch (Exception e){ }finally { lock.unlock(); } } public void remove(){ lock.lock(); try{ while (num == null){ //num 为null说明生产者没有生产数据,需要消费者等待生产者生产数据 remove.await(); } System.out.println(Thread.currentThread()+" 消费了一个货物 "+ num); num = null; //唤醒生产着,该生产了 add.signal(); }catch (Exception e){ }finally { lock.unlock(); } }}public class TestProvideAndFee { public static void main(String[] args) { final Depot depot = new Depot(); for(int i = 0; i < 10; i ++){ new Thread(new Runnable() { @Override public void run() { depot.add(); } }).start(); } for(int i = 0; i < 10; i ++){ new Thread(new Runnable() { @Override public void run() { depot.remove(); } }).start(); } }}有一个生产者生产出货物,那么就需要一个消费者去消费。
Thread[Thread-5,5,main] 生产出一个货物 0.4885486784752844Thread[Thread-11,5,main] 消费了一个货物 0.4885486784752844Thread[Thread-9,5,main] 生产出一个货物 0.34176842004713925Thread[Thread-16,5,main] 消费了一个货物 0.34176842004713925Thread[Thread-6,5,main] 生产出一个货物 0.5197749409203888Thread[Thread-13,5,main] 消费了一个货物 0.5197749409203888Thread[Thread-2,5,main] 生产出一个货物 0.2647941282185792Thread[Thread-17,5,main] 消费了一个货物 0.2647941282185792Thread[Thread-3,5,main] 生产出一个货物 0.6240931467512665Thread[Thread-15,5,main] 消费了一个货物 0.6240931467512665Thread[Thread-8,5,main] 生产出一个货物 0.7776132350280154Thread[Thread-10,5,main] 消费了一个货物 0.7776132350280154Thread[Thread-7,5,main] 生产出一个货物 0.8128245646683492Thread[Thread-19,5,main] 消费了一个货物 0.8128245646683492Thread[Thread-1,5,main] 生产出一个货物 0.9477304285260686Thread[Thread-14,5,main] 消费了一个货物 0.9477304285260686Thread[Thread-4,5,main] 生产出一个货物 0.9339639929998004Thread[Thread-12,5,main] 消费了一个货物 0.9339639929998004Thread[Thread-0,5,main] 生产出一个货物 0.7505226432601498Thread[Thread-18,5,main] 消费了一个货物 0.7505226432601498
- jdk-AbstractQueuedSynchronizer(四)
- jdk-AbstractQueuedSynchronizer(一)
- jdk-AbstractQueuedSynchronizer(二)
- jdk-AbstractQueuedSynchronizer(三)
- AbstractQueuedSynchronizer(四)——tryAcquireNanos/doAcquireNanos方法
- JDK之ReentrantLock,AbstractQueuedSynchronizer源码分析
- AbstractQueuedSynchronizer(AQS)
- AbstractQueuedSynchronizer(一)
- AbstractQueuedSynchronizer(二)
- AbstractQueuedSynchronizer
- AbstractQueuedSynchronizer
- AbstractQueuedSynchronizer
- AbstractQueuedSynchronizer
- AbstractQueuedSynchronizer
- AbstractQueuedSynchronizer
- AbstractQueuedSynchronizer
- AbstractQueuedSynchronizer
- AbstractQueuedSynchronizer
- k-近邻算法
- Linux_ActiveMQ 安装笔记
- POJ 1341 The Strongest Subchains 笔记
- PHP Web开发技巧
- 二叉搜索树---使用迭代器
- jdk-AbstractQueuedSynchronizer(四)
- installshield使用教程
- sass scss区别
- 神经网络之激活函数(Activation Function)
- 【Opencv_contri】物体追踪 (部分方法需要opencv3.1及以上)
- 推荐系统文章
- Leetcode 328 Odd Even Linked List
- Sqlmap注入技巧收集整理
- 中断的应用--一个工程产品案例的剖析