沈阳城市建设管理学校网站,做地理题的网站,网站后台更新怎么做,福州建设工程造价信息网类图结构
ReentrantLock是可重入的独占锁#xff0c;同时只能有一个线程可以获取该锁#xff0c;其他获取该锁的线程会被阻塞而被放入该锁的AQS阻塞队列里面。
首先看下ReentrantLock的类图以便对它的实现有个大致了解。 从类图可以看到#xff0c;ReentrantLock最终还是使…类图结构
ReentrantLock是可重入的独占锁同时只能有一个线程可以获取该锁其他获取该锁的线程会被阻塞而被放入该锁的AQS阻塞队列里面。
首先看下ReentrantLock的类图以便对它的实现有个大致了解。 从类图可以看到ReentrantLock最终还是使用AQS来实现的并且根据参数来决定其内部是一个公平还是非公平锁默认是非公平锁。 其中Sync类直接继承自AQS,它的子类NonfairSync和FairSync分别实现了获取锁的非公平与公平策略。
在这里AQS的state状态值表示线程获取该锁的可重入次数在默认情况下state的值为0表示当前锁没有被任何线程持有。
当一个线程第一次获取该锁时会尝试使用CAS设置state的值为1,如果CAS成功则当前线程获取了该锁然后记录该锁的持有者为当前线程。
在该线程没有释放锁的情况下第二次获取该锁后状态值被设置为2,这就是可重入次数。
在该线程释放该锁时会尝试使用CAS让状态值减1,如果减1后状态值为0,则当前线程释放该锁。
获取锁
void lock()方法
当一个线程调用该方法时说明该线程希望获取该锁。
如果锁当前没有被其他线程占用并且当前线程之前没有获取过该锁则当前线程会获取到该锁然后设置当前锁的拥有者为当前线程并设置AQS的状态值为1,然后直接返回。
如果当前线程之前已经获取过该锁则这次只是简单地把AQS的状态值加1后返回。
如果该锁已经被其他线程持有则调用该方法的线程会被放入AQS队列后阻塞挂起。 在如上代码中ReentrantLock的lock()委托给了sync类根据创建ReentrantLock构造函数选择sync的实现是NonfairSync还是FairSync,这个锁是一个非公平锁或者公平锁。
这里先看sync的子类NonfairSync的情况也就是非公平锁时。 在代码(1)中因为默认AQS的状态值为0,所以第一个调用Lock的线程会通过CAS设置状态值为1,CAS成功则表示当前线程获取到了锁然后setExclusiveOwnerThread设置该锁持有者是当前线程。
如果这时候有其他线程调用lock方法企图获取该锁CAS会失败然后会调用AQS的acquire方法。注意传递参数为1,这里再贴下AQS的acquire的核心代码。 之前说过AQS并没有提供可用的tryAcquire方法tryAcquire方法需要子类自己定制化所以这里代码(3)会调用ReentrantLock重写的tryAcquire方法。我们先看下非公平锁的代码。 首先代码(4)会查看当前锁的状态值是否为0,为0则说明当前该锁空闲那么就尝试CAS获取该锁将AQS的状态值从0设置为1,并设置当前锁的持有者为当前线程然后返回true。
如果当前状态值不为0则说明该锁已经被某个线程持有所以代码(5)查看当前线程是否是该锁的持有者如果当前线程是该锁的持有者则状态值加1,然后返回true,这里需要注意nextc0说明可重入次数溢出了。
如果当前线程不是锁的持有者则返回false,然后其会被放入AQS阻塞队列。
介绍完了非公平锁的实现代码回过头来看看非公平在这里是怎么体现的。
首先非公平是说先尝试获取锁的线程并不一定比后尝试获取锁的线程优先获取锁。
这里假设线程A调用lock()方法时执行到nonfairTryAcquire的代码(4),发现当前状态值不为0,所以执行代码(5),发现当前线程不是线程持有者则执行代码(6)返回false,然后当前线程被放入AQS阻塞队列。
这时候线程B也调用了lock()方法执行到nonfairTryAcquire的代码(4),发现当前状态值为0了(假设占有该锁的其他线程释放了该锁),所以通过CAS设置获取到了该锁。明明是线程A先请求获取该锁呀这就是非公平的体现。
这里线程B在获取锁前并没有查看当前AQS队列里面是否有比自己更早请求该锁的线程而是使用了抢夺策略。
那么下面看看公平锁是怎么实现公平的。公平锁的话只需要看FairSync重写的tryAcquire方法。 如以上代码所示公平的tryAcquire策略与非公平的类似不同之处在于代码(8)在设置CAS前添加了hasQueuedPredecessors方法该方法是实现公平性的核心代码代码如下。 在如上代码中如果当前线程节点有前驱节点则返回true,否则如果当前AQS队列为空或者当前线程节点是AQS的第一个节点则返回false。
其中如果ht则说明当前队列为空直接返回false。 如果h!t并且snull则说明有一个元素将要作为AQS的第一个节点入队列(回顾前面的内容enq函数的第一个元素入队列是两步操作首先创建一个哨兵头节点然后将第一个元素插入哨兵节点后面),那么返回true,如果h!t并且s!null和s.thread !Thread.currentThread()则说明队列里面的第一个元素不是当前线程那么返回true。
void locklnterruptibly()方法
该方法与lock()方法类似它的不同在于它对中断进行响应就是当前线程在调用该方法时如果其他线程调用了当前线程的interrupt()方法则当前线程会抛出InterruptedException异常然后返回。 boolean tryLock()方法
尝试获取锁如果当前该锁没有被其他线程持有则当前线程获取该锁并返回true,否则返回false。注意该方法不会引起当前线程阻塞。
如上代码与非公平锁的tryAcquire()方法代码类似所以tryLock()使用的是非公平策略。
boolean tryLock(long timeout,TimeUnit unit)
方法尝试获取锁与tryLock()的不同之处在于它设置了超时时间如果超时时间到没有获取到该锁则返回false。 释放锁
void unlock()方法
尝试释放锁如果当前线程持有该锁则调用该方法会让该线程对该线程持有的AQS状态值减1,如果减去1后当前状态值为0,则当前线程会释放该锁否则仅仅减1而已。
如果当前线程没有持有该锁而调用了该方法则会抛出IllegalMonitorStateException异常代码如下。
如代码(11)所示如果当前线程不是该锁持有者则直接抛出异常否则查看状态值是否为0,为0则说明当前线程要放弃对该锁的持有权则执行代码(12)把当前锁持有者设置为null。如果状态值不为0,则仅仅让当前线程对该锁的可重入次数减1。
加深理解 假如线程Thread1、Thread2和Thread3同时尝试获取独占锁ReentrantLock,假设Thread1获取到了则Thread2和Thread3就会被转换为Node节点并被放入ReentrantLock对应的AQS阻塞队列而后被阻塞挂起。
假设Thread1获取锁后调用了对应的锁创建的条件变量1,那么Thread1就会释放获取到的锁然后当前线程就会被转换为Node节点插入条件变量1的条件队列。
由于Thread1释放了锁所以阻塞到AQS队列里面的Thread2和Thread3就有机会获取到该锁假如使用的是公平策略那么这时候Thread2会获取到该锁从而从AQS队列里面移除Thread2对应的Node节点。