Java基础多线程(六)Lock接口 红太狼 2022-02-03 02:15 166阅读 0赞 ### Lock接口 ### * 概述 * 使用示例 * 源码分析 * * ReentrantLock类结构 * ReentrantLock如何实现同步的? * ReentrantLock如何实现可重入? * ReentrantLock是如何实现公平锁和非公平锁的? * ReentrantLock中公平锁是如何保证公平性的? # 概述 # 摘自博客:[http://www.cnblogs.com/skywang12345/p/3496098.html][http_www.cnblogs.com_skywang12345_p_3496098.html] > JUC包中的 Lock 接口支持那些语义不同(重入、公平等)的锁规则。所谓语义不同,是指锁可是有"公平机制的锁"、“非公平机制的锁”、“可重入的锁"等等。“公平机制"是指"不同线程获取锁的机制是公平的”,而"非公平机制"则是指"不同线程获取锁的机制是非公平的”,"可重入的锁"是指同一个锁能够被一个线程多次获取。 显式锁是jdk1.5开始引入的`排他锁`,它的作用于内部锁(synchronized)相同,它提供了一些内部锁不具备的特性,使用起来更加灵活。类java.util.concurrent.locks.`ReentrantLock`是Lock接口的默认实现类。 # 使用示例 # 一个线程安全的简单抢票系统 内部锁实现: public class Demo implements Runnable{ int ticket = 100; @Override public synchronized void run() { while (ticket>0) { ticket--; System.out.println(Thread.currentThread().getName() + "抢到了第" + ticket + "张票"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { Demo demo = new Demo(); for (int i = 0; i < 10; i++) { new Thread(demo).start(); } } } 显式锁实现: public class Demo implements Runnable { int ticket = 100; Lock lock = new ReentrantLock(); @Override public void run() { try { lock.lock(); if (ticket > 0) { ticket--; System.out.println(Thread.currentThread().getName() + "抢到了第" + ticket + "张票"); Thread.sleep(10); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { Demo demo = new Demo(); for (int i = 0; i < 10; i++) { new Thread(demo).start(); } } } 显式锁的使用步骤大致为: * 创建Lock接口的实现类,一般默认使用的是ReentrantLock,从字面上可以看出它是一个可重入锁。 * 在访问需要同步的代码块之前需要先申请响应的显式锁,一般调用的是lock()方法。 * 访问结束同步代码块后,`必须要手动调用unlock() 释放锁`,通常锁的释放放在finally中执行,可以避免锁的泄露。 # 源码分析 # ## ReentrantLock类结构 ## 属性: private static final long serialVersionUID = 7373984872572414699L; /** Synchronizer providing all implementation mechanics */ // ReentrantLock的同步机制都由Sync对象来提供 private final Sync sync; 内部类: // 此锁的同步控制基础。子类化为下面的公平和非公平版本。使用AQS状态表示锁上的持有数。 abstract static class Sync extends AbstractQueuedSynchronizer //提供了公平锁的实现 static final class FairSync extends Sync //提供了非公平锁的实现 static final class noFairSync extends Sync 重点讲解内部类Sync,它继承了AQS,AQS实现了一个基于CLH队列的同步器框架。 ## ReentrantLock如何实现同步的? ## `结论:通过AQS中的state属性值来表示当前lock是否被持有,如果state不为0且当前申请锁的线程不是该锁的持有者则该线程进入等待队列,无法执行临界区的代码。` 简单分析源码(这里选择非公平锁做示例,原理相同): //ReentrantLock中的方法 public void lock() { sync.lock(); } //调用了noFailLock中实现的方法 final void lock() { //CAS修改state的值,其实就是尝试获取锁,成功则进入临界区访问 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } //AQS中的acquire方法 public final void acquire(int arg) { /** tryAcquire方法尝试获取锁,获取成功返回true,被!反以后则不会阻塞,直接执行临界区代码, 如果获取锁失败则继续执行如下方法: addWaiter方法将当前线程加入等待队列中,返回值为队列中的结点对象。 acquireQueued中令当前获取锁的线程自旋获取锁一定次数后进入阻塞状态 */ if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } //noFailLock中实现的tryAcquire调用了nonfairTryAcquire final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //如果锁无线程持有,尝试获取,成功则返回true,线程执行临界区代码 if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //判断当前线程是否是持有该锁的线程,是的话进行重入锁操作,status++ else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } //没有获取到锁返回false return false; } ## ReentrantLock如何实现可重入? ## `结论:通过对AQS中的state属性+1表示重入了几次,当持有锁的线程再次申请锁时,会判断AbstractOwnableSynchronizer中的exclusiveOwnerThread属性是不是currentThread。` 分析核心代码(非公平锁为例): final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //判断当前申请锁的线程是不是持有该锁的线程 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; //判断重入此时是否超过限度 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); //设定state setState(nextc); return true; } return false; } ## ReentrantLock是如何实现公平锁和非公平锁的? ## `结论:在构造方法中指定fai参数,从而选择创建的sync对象是FailSync或NoFailSync。ReentrantLock中的内部类Sync有两个实现类一个是公平锁,一个是非公平锁,最后是公平锁还是非公平锁取决于sync对象是"FairSync的实例"还是"NonFairSync的实例。` public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } ## ReentrantLock中公平锁是如何保证公平性的? ## ①`结合非公平锁来说明:非公平锁在申请锁时会直接去获取锁,此时可能等待队列中的线程正在尝试获取但是还没获取到,有可能会被新来的线程抢先。而公平锁会先判断当前锁的等待队列中是否有线程正在等待,如果有的话会将该线程添加到队尾等待。` ②`其次会判断等待线程被唤醒时的前驱结点是不是头结点,防止线程是因为中断唤醒而影响公平性。` * 解释①: 非公平锁: final void lock() { //直接尝试获取 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //直接尝试获取 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if { 重入锁部分代码} return false; } 公平锁: protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // hasQueuedPredecessors判断等待队列中是否有线程 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if { 可重入锁代码....} return false; } * 解释②: > * 公平锁是按照申请锁的顺序来分配锁,先到先得。那么在等待队列中阻塞的线程是有可能被中断唤醒的,那么后来的线程被中断唤醒后先获取锁显然是不符合公平性原则的。那么就需要判断被唤醒的线程是正常唤醒或者中断唤醒再让他获取锁。 > * 首先需要了解: > > > 1. 当一个线程获取到锁以后它会被设置成head节点 > > 2. 当线程释放锁时,它唤醒的是head节点的后继节点。 > > 根据这两条原则,只需要判断被唤醒的这个线程是不是head节点的后继节点就行了 private final boolean parkAndCheckInterrupt() { //线程在这行代码中被阻塞 LockSupport.park(this); //唤醒后执行interrupted方法,返回当前线程的中断状态,并清除中断状态。 return Thread.interrupted(); } final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { // predecessor方法获取的是前驱结点 final Node p = node.predecessor(); /* 如果是被正常唤醒的线程所在的位置是头结点的后继,那么p == head,如果是被 中断唤醒的线程,所在的位置就不一定了,如果 p != head则违反了公平性原则,就会执行 后序的parkAndCheckInterrupt重新阻塞。 */ if (p == head && tryAcquire(arg)) { // 获取到锁的节点会被设置成头结点 setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } [http_www.cnblogs.com_skywang12345_p_3496098.html]: http://www.cnblogs.com/skywang12345/p/3496098.html
还没有评论,来说两句吧...