ReentrantLock公正锁和不公正锁完成原理

公正锁会获取锁时会判别堵塞行列里是否有线程再等候,若有获取锁就会失利,而且会参加堵塞行列

非公正锁获取锁时不会判别堵塞行列是否有线程再等候,所以对于现已在等候的线程来说是不公正的,但假如是由于其它原因没有竞赛到锁,它也会参加堵塞行列

进入堵塞行列的线程,竞赛锁时都是公正的,应为行列为先进先出(FIFO)

默许完成的对错公正锁

public ReentrantLock() {
//非公正锁    sync = new NonfairSync();}

还供给了其他一种办法,可传入一个boolean值,true时为公正锁,false时为非公正锁

//公正锁public ReentrantLock(boolean fair) {    sync = fair ? new FairSync() : new NonfairSync();}

非公正锁

非公正锁获取锁nonfairTryAcquire办法,对锁状况进行了判别,并没有把锁参加同步行列中

    static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;        
final void lock() {            
//比较当时状况 假如持有者是当时线程            
if (compareAndSetState(0, 1))               
 setExclusiveOwnerThread(Thread.currentThread());            
else               
 //假如不是 测验获取锁             
   acquire(1);        }      
  protected final boolean tryAcquire(int acquires) {           
 //获取锁            return nonfairTryAcquire(acquires);        }  
  }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");               
 setState(nextc);               
 return true;          
  }         
   return false;    
    }

公正锁

代码和nonfairTryAcquire仅有的不同在于增加了hasQueuedPredecessors办法的判别

static final class FairSync extends Sync {
 private static final long serialVersionUID = -3000897897090466540L;     
  protected final boolean tryAcquire(int acquires) {         
  //获取当时线程           
 final Thread current = Thread.currentThread();           
 int c = getState();           
 //判别当时目标是否被持有           
 if (c == 0) {                
//假如等候行列为空 而且运用CAS获取锁成功   
否则回来false然后从行列中获取节点                
if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {                  
  //把当时线程持有                  
  setExclusiveOwnerThread(current);                  
  return true;               
 }           
 }          
 //若被持有 判别锁是否是当时线程  可重入锁的要害代码           
 else if (current == getExclusiveOwnerThread()) {              
  //计数加1 回来                
int nextc = c + acquires;               
 if (nextc < 0)                  
  throw new Error("Maximum lock count exceeded");               
 setState(nextc);               
 return true;           
 }           
 //不是当时线程持有 履行           
 return false;       
 }  
  }

acquire()获取锁

    public final void acquire(int arg) {
 //假如当时线程测验获取锁失利而且 参加把当时线程参加了等候行列         
if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))          
  //先中止当时线程           
 selfInterrupt();  
  }

要害代码

便是tryAcquire办法中hasQueuedPredecessors判别行列是否有其他节点

public final boolean hasQueuedPredecessors() {
 Node t = tail; 
// Read fields in reverse initialization order   
 Node h = head;    Node s;   
 return h != t &&       
 ((s = h.next) == null || s.thread != Thread.currentThread());
}

可重入性完成原理

在线程获取锁的时分,假如现已获取锁的线程是当时线程的话则直接再次获取成功

由于锁会被获取n次,那么只要锁在被开释同样的n次之后,该锁才算是彻底开释成功

1、获取锁办法

 protected final boolean tryAcquire(int acquires) {
 //获取当时线程           
 final Thread current = Thread.currentThread();           
 int c = getState();           
 //判别当时目标是否被持有           
 if (c == 0) {             
 //...略            }           
//若被持有 判别锁是否是当时线程  可重入锁的要害代码           
 else if (current == getExclusiveOwnerThread()) {               
 //计数加1 回来                
int nextc = c + acquires;               
 if (nextc < 0)                   
 throw new Error("Maximum lock count exceeded");                
setState(nextc);                
return true;           
 }          
  //不是当时线程持有 履行         
   return false;     
   }

每次假如获取到的都是当时线程这儿都会计数加1

开释锁

protected final boolean tryRelease(int releases) {
//每次开释都减1  
  int c = getState() - releases;   
 if (Thread.currentThread() != getExclusiveOwnerThread())     
   throw new IllegalMonitorStateException();   
 boolean free = false;   
 //等于0才开释锁成功   
 if (c == 0) {        
free = true;        
setExclusiveOwnerThread(null);  
  }   
 setState(c); 
   return free;
}

假如锁被获取n次,也要开释了n次,只要彻底开释才会回来false

1.ReentrantLock详解

相对于synchronized 它具备如下特色

  • 可中止
  • 能够设置超时时刻
  • 能够设置为公正锁
  • 支撑多个条件变量
  • 与 synchronized 相同,都支撑可重入

基本语法

// 获取ReentrantLock目标
private ReentrantLock lock = new ReentrantLock();
// 加锁 获取不到锁一向等候直到获取锁
lock.lock();
try {
    // 临界区
    // 需求履行的代码
}finally {
    // 开释锁 假如不开释其他线程就获取不到锁
    lock.unlock();
}
​
•    /**
     * 获取锁。
          * 假如锁不行用,则当时线程将因线程调度而被禁用,并处于休眠状况,直到取得锁停止。
          */
        void lock();
​
    /**
    当时线程被中止,获取锁
    获取锁(假如可用)并当即回来。
    假如锁不行用,则当时线程将因线程调度而被禁用,并处于休眠状况,直到产生以下两种状况之一:
    1. 锁被当时线程获取;或许
        (获取锁正常回来)
    2.其他线程中止当时线程,并支撑当时线程在获取锁时中止.
    假如当时线程:
    在进入此办法时设置其间止状况;或获取锁时中止,支撑锁获取中止,
    然后抛出InterruptedException并铲除当时线程的中止状况。 
    (意思睡觉时其他线程中止了当时线程获取锁直接铲除当时线程睡觉状况)
          */
        void lockInterruptibly() throws InterruptedException;
​
     /**
    只要在调用时它是空闲的时才获取锁。 (意思锁或许拿不到 lock是一定能拿得到)
    获取锁(假如可用),并当即回来值true。假如此办法不行用,则该办法将当即回来false。
    此办法的典型用法是:
    Lock lock = ...;
     if (lock.tryLock()) {
       try {
         // manipulate protected state
       } finally {
         lock.unlock();
       }
     } else {
       // perform alternative actions
     }
     这种用法保证锁在被获取时被解锁,而在未取得锁时不测验解锁。
     */
    boolean tryLock();
    /**
    tryLock重载办法
    假如锁在给定的等候时刻内空闲而且当时线程没有中止,则获取该锁。
    假如指定的等候时刻为false,则回来的值为false。假如时刻小于或等于零,则该办法底子不会等候。
    time–等候锁定的最长时刻
    unit–时刻参数的时刻单位
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
     /**
     开释锁。
     留意:
    锁完成一般会对线程开释锁施加约束(一般只要锁的持有者才干开释锁),假如违反了约束,
    则或许会抛出(未查看的)反常。任何约束和反常类型都必须由该锁完成记录。
     */
    void unlock();
    /**
    回来绑定到此Lock实例的新条件实例。
​
    在等候条件之前,锁必须由当时线程持有。打电话给Condition.await() 将在等候之前主动开释锁,
    并在等候回来之前从头获取锁。
    施留意事项
    条件实例的确切操作取决于锁完成,而且必须由该完成记录。
    Condition  完成 wait notify 的功能 而且功能更强壮
     */
    Condition newCondition();
​

1.1 可重入

可重入锁是指同一个线程假如首次取得了这把锁,那么由于它是这把锁的具有者,因而 有权利再次获取这把锁 假如是不行重入锁,那么第2次取得锁时,自己也会被锁挡住

/**
​
 * Description: ReentrantLock 可重入锁, 同一个线程能够屡次取得锁目标
 */
@Slf4j(topic = "z.ReentrantTest")
public class ReentrantTest {
​
    private static ReentrantLock lock = new ReentrantLock();
​
    public static void main(String[] args) {
        // 假如有竞赛就进入`堵塞行列`, 一向等候着,不能被打断
        // 主线程main取得锁
        lock.lock();
        try {
            log.debug("entry main...");
            m1();
        } finally {
            lock.unlock();
        }
    }
​
    private static void m1() {
        lock.lock();
        try {
            log.debug("entry m1...");
            m2();
        } finally {
            lock.unlock();
        }
    }
​
    private static void m2() {
        log.debug("entry m2....");
    }
}
​

运转结果

2022-03-11 21:15:34 [main] - entry main...
2022-03-11 21:15:34 [main] - entry m1...
2022-03-11 21:15:34 [main] - entry m2....
​
Process finished with exit code 0

synchronized的可重入

static final Object obj = new Object();
public static void method1() {
     synchronized( obj ) {
         // 同步块 A
         method2();
     }
}
public static void method2() {
     synchronized( obj ) {
         // 同步块 B
     }
}

1.2 可中止 lockInterruptibly()

synchronized 和 reentrantlock.lock() 的锁, 是不行被打断的; 也便是说其他线程现已取得了锁, 线程就需求一向等候下去,不能中止,直到取得到锁才运转。

经过reentrantlock.lockInterruptibly(); 能够经过调用堵塞线程的t1.interrupt();办法 打断。

/**
 * @ClassName ReentrantTest1
 * @author: shouanzh
 * @Description ReentrantLock, 演示RenntrantLock中的可打断锁办法 lock.lockInterruptibly();
 * @date 2022/3/11 21:31
 */
@Slf4j
public class ReentrantTest1 {
​
    private static ReentrantLock lock = new ReentrantLock();
​
    public static void main(String[] args) throws InterruptedException {
​
        Thread thread = new Thread(() -> {
            try {
                // 假如没有竞赛那么此办法就会获取 lock 目标锁
                // 假如有竞赛就进入堵塞行列,能够被其它线程用 interruput 办法打断
                log.debug("测验取得锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("t1线程没有取得锁,被打断...return");
                return;
            }
            try {
                log.debug("t1线程取得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        // t1启动前 主线程先取得了锁
        lock.lock();
        thread.start();
        Thread.sleep(1000);
        log.debug("interrupt...打断t1");
        thread.interrupt();
    }
​
}
2022-03-11 21:42:43 [t1] - 测验取得锁
2022-03-11 21:42:44 [main] - interrupt...打断t1
2022-03-11 21:42:44 [t1] - t1线程没有取得锁,被打断...return
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:900)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1225)
    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
    at com.concurrent.reentrantlocktest.ReentrantTest1.lambda$main$0(ReentrantTest1.java:25)
    at java.lang.Thread.run(Thread.java:748)
​
Process finished with exit code 0

测验运用lock.lock()不行以从堵塞行列中打断, 一向等候其他线程开释锁

@Slf4j
public class ReentrantTest1 {
​
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
//            try {
//                // 假如没有竞赛那么此办法就会获取 lock 目标锁
//                // 假如有竞赛就进入堵塞行列,能够被其它线程用 interruput 办法打断
//                log.debug("测验取得锁");
//                lock.lockInterruptibly();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//                log.debug("t1线程没有取得锁,被打断...return");
//                return;
//            }
​
            log.debug("测验取得锁");
            lock.lock();
            try {
                log.debug("t1线程取得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        // t1启动前 主线程先取得了锁
        lock.lock();
        thread.start();
        Thread.sleep(1000);
        log.debug("interrupt...打断t1");
        thread.interrupt();
    }
​
}
​

调用thread.interrupt();后 thread线程并没被打断。

ReentrantLock详细解剖+公平锁和不公平锁实现

1.3 设置超时时刻 tryLock()

运用 lock.tryLock() 办法会回来获取锁是否成功。假如成功则回来true,反之则回来false。

而且tryLock办法能够设置指定等候时刻,参数为:tryLock(long timeout, TimeUnit unit) , 其间timeout为最长等候时刻,TimeUnit为时刻单位

获取锁的过程中, 假如超越等候时刻, 或许被打断, 就直接从堵塞行列移除, 此刻获取锁就失利了, 不会一向堵塞着 ! (能够用来完成死锁问题)

不设置等候时刻, 当即失利

/**
 * @ClassName ReentrantTest2
 * @author: shouanzh
 * @Description 测验锁超时
 * @date 2022/3/11 22:18
 */
@Slf4j
public class ReentrantTest2 {
​
    private static ReentrantLock lock = new ReentrantLock();
​
    public static void main(String[] args) throws InterruptedException {
​
        Thread t1 = new Thread(() -> {
            log.debug("测验取得锁");
            // 此刻必定获取失利, 由于主线程现已取得了锁目标
            if (!lock.tryLock()) {
                log.debug("获取马上失利,回来");
                return;
            }
            try {
                log.debug("取得到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        // 主线程先取得锁
        lock.lock();
        log.debug("取得到锁");
        t1.start();
        // 主线程2s之后才开释锁
        Thread.sleep(2000);
        log.debug("开释了锁");
        lock.unlock();
    }
​
}
2022-03-11 22:20:09 [main] - 取得到锁
2022-03-11 22:20:09 [t1] - 测验取得锁
2022-03-11 22:20:09 [t1] - 获取马上失利,回来
2022-03-11 22:20:11 [main] - 开释了锁
​
Process finished with exit code 0

设置等候时刻

/**
 * @ClassName ReentrantTest2
 * @author: shouanzh
 * @Description 测验锁超时
 * @date 2022/3/11 22:18
 */
@Slf4j
public class ReentrantTest2 {
​
    private static ReentrantLock lock = new ReentrantLock();
​
    public static void main(String[] args) throws InterruptedException {
​
        Thread t1 = new Thread(() -> {
            log.debug("测验取得锁");
            // 此刻必定获取失利, 由于主线程现已取得了锁目标
            try {
                // 等候1s
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("等候1s获取不到锁");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("获取不到锁");
                return;
            }
            try {
                log.debug("取得到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("取得到锁");
        t1.start();
        // 主线程2s之后才开释锁
        Thread.sleep(2000);
        log.debug("开释了锁");
        lock.unlock();
    }
​
}
2022-03-11 22:32:32 [main] - 取得到锁
2022-03-11 22:32:32 [t1] - 测验取得锁
2022-03-11 22:32:33 [t1] - 等候1s获取不到锁
2022-03-11 22:32:34 [main] - 开释了锁
​
Process finished with exit code 0

1.4 经过lock.tryLock()来处理, 哲学家就餐问题

lock.tryLock(时刻) : 测验获取锁目标, 假如超越了设置的时刻, 还没有获取到锁, 此刻就退出堵塞行列, 并开释掉自己具有的锁

/**
 * Description: 运用了ReentrantLock锁, 该类中有一个tryLock()办法, 在指定时刻内获取不到锁目标, 就从堵塞行列移除,不用一向等候。
 *              当获取了左手边的筷子之后, 测验获取右手边的筷子, 假如该筷子被其他哲学家占用, 获取失利, 此刻就先把自己左手边的筷子,
 *              给开释掉. 这样就避免了死锁问题
 */
@Slf4j(topic = "z.PhilosopherEat")
public class PhilosopherEat {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("苏格拉底", c1, c2).start();
        new Philosopher("柏拉图", c2, c3).start();
        new Philosopher("亚里士多德", c3, c4).start();
        new Philosopher("赫拉克利特", c4, c5).start();
        new Philosopher("阿基米德", c5, c1).start();
    }
}
​
@Slf4j(topic = "z.Philosopher")
class Philosopher extends Thread {
    final Chopstick left;
    final Chopstick right;
​
    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }
    @Override
    public void run() {
        while (true) {
            // 取得了左手边筷子 (针对五个哲学家, 它们刚开端必定都可取得左筷子)
            if (left.tryLock()) {
                try {
                    // 此刻发现它的right筷子被占用了, 运用tryLock(), 
                    // 测验获取失利, 此刻它就会将自己左筷子也开释掉
                    // 临界区代码
                    if (right.tryLock()) { //测验获取右手边筷子, 假如获取失利, 则会开释左面的筷子
                        try {
                            eat();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock(); // 则会开释左面的筷子
                }
            }
        }
    }
    private void eat() throws InterruptedException {
        log.debug("eating...");
        Thread.sleep(500);
    }
}
​
// 承继ReentrantLock, 让筷子类称为锁
class Chopstick extends ReentrantLock {
​
    String name;
    public Chopstick(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

1.5 公正锁 new ReentrantLock(true)

  • ReentrantLock默许对错公正锁, 能够指定为公正锁new ReentrantLock(true)。
  • 在线程获取锁失利,进入堵塞行列时,先进入的会在锁被开释后先取得锁。这样的获取办法便是公正的。一般不设置ReentrantLock为公正的, 会降低并发度
  • Synchronized底层的Monitor锁便是不公正的, 和谁先进入堵塞行列是没有关系的。

公正锁

多个线程依照请求锁的次序去取得锁,线程会直接进入行列去排队,永远都是行列的第一位才干得到锁。 长处:一切的线程都能得到资源,不会饿死在行列中。 缺陷:吞吐量会下降很多,行列里面除了第一个线程,其他的线程都会堵塞,cpu唤醒堵塞线程的开销会很大。

非公正锁

多个线程去获取锁的时分,会直接去测验获取,获取不到,再去进入等候行列,假如能获取到,就直接获取到锁。 长处:能够削减CPU唤醒线程的开销,整体的吞吐功率会高点,CPU也不用取唤醒一切线程,会削减唤起线程的数量。 缺陷:你们或许也发现了,这样或许导致行列中间的线程一向获取不到锁或许长时刻获取不到锁,导致饿死。

1.6 条件变量 Condition

传统目标等候调集只要一个 waitSet, Lock能够经过newCondition()办法 生成多个等候调集Condition目标。 Lock和Condition 是一对多的关系

synchronized 中也有条件变量,便是咱们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet等候 ReentrantLock的条件变量比 synchronized强壮之处在于,它是支撑多个条件变量的,这就比方 synchronized 是那些不满足条件的线程都在一间休息室等音讯 而 ReentrantLock 支撑多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来 唤醒

运用流程

1.await 前需求 取得锁 2.await 履行后,会开释锁,进入 conditionObject (条件变量)中等候 3.await 的线程被唤醒(或打断、或超时)取从头竞赛 lock 锁 4.竞赛 lock 锁成功后,从 await 后持续履行 5.signal 办法用来唤醒条件变量(等候室)汇总的某一个等候的线程 6.signalAll办法, 唤醒条件变量(休息室)中的一切线程

    /**
     使当时线程等候,直到宣布siganal信号或中止。
     与此condition相关联的Lock将被主动开释,当时线程出于线程调度意图被禁用并处于休眠状况,
     直到产生以下四种状况之一:
        1,另一个线程为此condition调用signal办法,而且当时线程刚好被选为要唤醒的线程;
        2,其他线程为此condition调用signalAll办法
        3.其他线程中止当时线程,支撑线程挂起中止
        4.呈现“虚伪唤醒”。
    跟wait相同被唤醒仍是的竞赛锁竞赛到了才干履行await之后的代码
    假如当时线程:在进入此办法时设置其间止状况;或等候时中止,支撑线程挂起中止,
    然后抛出InterruptedException并铲除当时线程的中止状况。
    在第一种状况下,没有规定是否在开释锁之前进行中止测验。
     */
    void await() throws InterruptedException;
​
    /**
        与await 相同  不行被中止
        与此condition相关联的Lock将被主动开释,当时线程出于线程调度意图被禁用并处于休眠状况,
        直到产生以下四种状况之一:
        1,另一个线程为此condition调用signal办法,而且当时线程刚好被选为要唤醒的线程;
        2,其他线程为此condition调用signalAll办法
        4.呈现“虚伪唤醒”
     */
    void awaitUninterruptibly();
     /**
    使当时线程等候,直到宣布siganal信号或中止,或指定的等候时刻曩昔。
    1,另一个线程为此condition调用signal办法,而且当时线程刚好被选为要唤醒的线程;
       2,其他线程为此condition调用signalAll办法
       3.其他线程中止当时线程,支撑线程挂起中止
       4.呈现“虚伪唤醒”。
       5.指定的时刻到了,会被唤醒
     如下
     nanos = theCondition.awaitNanos(500); 
     nanos = 300 
     意思便是 等候200纳秒就被唤醒
     回来值为500-200 回来值是估值 近似 其次该等候时刻运用纳秒为了更精确
​
    假如回来时给定供给的nanoTimeout值,则该办法回来要等候的纳秒数的估计值,假如超时,
    则回来小于或等于零的值。此值可用于确定在等候回来但等候的条件仍不成立的状况下是否从头
    等候以及从头等候多长时刻。典型的办法有以下几种:
    boolean aMethod(long timeout, TimeUnit unit) {
    long nanos = unit.toNanos(timeout);//转成纳秒
    lock.lock();
    try {
     while (!conditionBeingWaitedFor()) {
       if (nanos <= 0L) // <=0 时 回来超时了  
         return false;
       nanos = theCondition.awaitNanos(nanos);
     }
     // ...
      } finally {
       lock.unlock();
      }
    }
    Params:nanoTimeout–等候的最长时刻,以纳秒为单位
    return:nanoTimeout值的估计值减去从该办法回来时所花费的时刻。正值能够用作后续调用此办法以完成等候所需时刻的参数。小于或等于零的值表示没有时刻剩下。
     */
    long awaitNanos(long nanosTimeout) throws InterruptedException;
​
​
     /**
        使当时线程等候,直到宣布信号或中止,或指定的等候时刻曩昔。这种办法在行为上等同于:
        awaitNanos(unit.toNanos(time)) > 0
     * @param 等候的最长时刻
     * @param 时刻参数的时刻单位
     * @return 假如超越等候时刻,则为false,否则为true
     */
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    /*
        使当时线程等候,直到宣布信号或中止,或指定的截止时刻曩昔。
         boolean aMethod(Date deadline) {
           boolean stillWaiting = true;
           lock.lock();
           try {
             while (!conditionBeingWaitedFor()) {
               if (!stillWaiting)
                 return false;
               stillWaiting = theCondition.awaitUntil(deadline);
             }
             // ...
           } finally {
             lock.unlock();
           }
         }
    Params 截止日期–等候的绝对时刻
    return 假如回来时已过截止日期,则为false,否则为true
    */
    boolean awaitUntil(Date deadline) throws InterruptedException;
​
    /**
        唤醒一个等候的线程。
        假如有多个线程正在此condition实例中等候,则挑选一个线程进行唤醒。
        然后,该线程必须在从await回来之前从头获取锁。(意思便是被唤醒仍是的竞赛锁)
        signal调用必须在lock 获取 和锁开释之间
     */
    void signal();
    /**
    唤醒一切等候的线程。
    假如有线程在此condition实例下等候,那么它们都将被唤醒。每个线程必须从头获取锁,然后才干从await回来。(意思便是被唤醒仍是的竞赛锁)
    signalAll调用必须在lock 获取 和锁开释之间
     */
    void signalAll();
​

代码举例:

/**
 * Description: ReentrantLock能够设置多个条件变量(多个休息室), 相对于synchronized底层monitor锁中waitSet
 */
@Slf4j(topic = "z.ConditionVariable")
public class ConditionVariable {
    private static boolean hasCigarette = false;
    private static boolean hasTakeout = false;
    private static final ReentrantLock lock = new ReentrantLock();
​
    // 等候烟的休息室(条件变量)
    static Condition waitCigaretteSet = lock.newCondition();
    // 等外卖的休息室(条件变量)
    static Condition waitTakeoutSet = lock.newCondition();
​
    public static void main(String[] args) throws InterruptedException {
​
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("有烟没?[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        // 此刻小南进入到 等烟的休息室
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("烟来咯, 能够开端干活了");
            } finally {
                lock.unlock();
            }
        }, "小南").start();
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("外卖送到没?[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("没外卖,先歇会!");
                    try {
                        // 此刻小女进入到 等外卖的休息室
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外卖来咯, 能够开端干活了");
            } finally {
                lock.unlock();
            }
        }, "小女").start();
        Thread.sleep(1000);
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("送外卖的来咯~");
                hasTakeout = true;
                // 唤醒等外卖的小女线程
                waitTakeoutSet.signal();
            } finally {
                lock.unlock();
            }
        }, "送外卖的").start();
        Thread.sleep(1000);
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("送烟的来咯~");
                hasCigarette = true;
                // 唤醒等烟的小南线程
                waitCigaretteSet.signal();
            } finally {
                lock.unlock();
            }
        }, "送烟的").start();
    }
}
​

运转结果

2022-03-11 23:31:22 [小南] - 有烟没?[false]
2022-03-11 23:31:22 [小南] - 没烟,先歇会!
2022-03-11 23:31:22 [小女] - 外卖送到没?[false]
2022-03-11 23:31:22 [小女] - 没外卖,先歇会!
2022-03-11 23:31:23 [送外卖的] - 送外卖的来咯~
2022-03-11 23:31:23 [小女] - 外卖来咯, 能够开端干活了
2022-03-11 23:31:24 [送烟的] - 送烟的来咯~
2022-03-11 23:31:24 [小南] - 烟来咯, 能够开端干活了
​
Process finished with exit code 0

2.同步形式之次序操控

2.1 固定运转次序

比方必须先打印2再打印1

运用wait/notify来完成次序打印 2, 1

/**
 * Description: 运用wait/notify来完成次序打印 2, 1
 */
@Slf4j(topic = "z.SyncPrintWaitTest")
public class SyncPrintWaitTest {
​
    public static final Object lock = new Object();
    // t2线程开释履行过
    public static boolean t2Runned = false;
​
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                while (!t2Runned) {
                    try {
                        // 进入等候(waitset), 会开释锁
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("1");
            }
        }, "t1");
​
        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                log.debug("2");
                t2Runned = true;
                lock.notify();
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}

运用LockSupport中的park/unpark

/**
 * Description: 运用LockSupport中的park,unpark来完成, 次序打印 2, 1
 */
@Slf4j(topic = "z.SyncPrintWaitTest")
public class SyncPrintWaitTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
            log.debug("1");
        }, "t1");
        t1.start();
​
        new Thread(() -> {
            log.debug("2");
            LockSupport.unpark(t1);
        }, "t2").start();
    }
}
​

运用ReentrantLock的await/signal

/**
 * @ClassName SyncPrintWaitTest2
 * @author: shouanzh
 * @Description 运用ReentrantLock的await/signal 次序打印 2、1
 * @date 2022/3/12 11:01
 */
@Slf4j
public class SyncPrintWaitTest2 {
​
    private static final ReentrantLock LOCK = new ReentrantLock();
    static Condition condition = LOCK.newCondition();
    // t2线程 是否履行过
    public static boolean t2Runned = false;
​
    public static void main(String[] args) {
​
        Thread t1 = new Thread(() -> {
            LOCK.lock();
            try {
                while (!t2Runned) {
                    condition.await();
                }
                log.debug("1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                LOCK.unlock();
            }
        },"t1");
​
​
        Thread t2 = new Thread(() -> {
            LOCK.lock();
            try {
                log.debug("2");
                t2Runned = true;
                condition.signal();
            } finally {
                LOCK.unlock();
            }
        },"t2");
        t1.start();
        t2.start();
    }
}

2.2 替换输出

线程1输出a5次,线程2输出b5次,线程3输出c5次。现在要求输出 abcabcabcabcabc怎样完成

wait/notify版别

/**
 * @ClassName AlternatePrint
 * @author: shouanzh
 * @Description 线程1输出a5次,线程2输出b5次,线程3输出c5次。现在要求输出 abcabcabcabcabc怎样完成
 * @date 2022/3/12 11:19
 */
@Slf4j
public class AlternatePrint {
​
    public static void main(String[] args) {
​
        WaitNotify waitNotify = new WaitNotify(1, 5);
        new Thread(() -> {
            waitNotify.print("a", 1, 2);
        }, "a线程").start();
        new Thread(() -> {
            waitNotify.print("b", 2, 3);
        }, "b线程").start();
        new Thread(() -> {
            waitNotify.print("c", 3, 1);
        }, "c线程").start();
​
    }
​
}
​
class WaitNotify {
​
    // 等候符号
    private int flag;
    // 循环次数
    private int loopNumber;
    public WaitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }
    /**
     * print
     *
     * @param str      打印的内容
     * @param waitFlag 等候符号
     * @param nextFlag 下一个符号
     */
     /**
     * 输出内容    等候符号    下一个符号
     * a           1          2
     * b           2          3
     * c           3          1
     */
    public void print(String str, int waitFlag, int nextFlag) {
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this) {
                while (waitFlag != flag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(str);
                flag = nextFlag;
                this.notifyAll();
            }
        }
    }
}
abcabcabcabcabc
Process finished with exit code 0

await/signal版别

/**
 * @ClassName AlternatePrintTest2
 * @author: shouanzh
 * @Description 线程1输出a5次,线程2输出b5次,线程3输出c5次。现在要求输出 abcabcabcabcabc怎样完成
 * @date 2022/3/12 11:49
 */
@Slf4j
public class AlternatePrintTest2 {
​
    public static void main(String[] args) throws InterruptedException {
​
        AwaitSignal awaitSignal = new AwaitSignal(5);
        Condition a_condition = awaitSignal.newCondition();
        Condition b_condition = awaitSignal.newCondition();
        Condition c_condition = awaitSignal.newCondition();
        new Thread(() -> {
            awaitSignal.print("a", a_condition, b_condition);
        }, "a").start();
        new Thread(() -> {
            awaitSignal.print("b", b_condition, c_condition);
        }, "b").start();
        new Thread(() -> {
            awaitSignal.print("c", c_condition, a_condition);
        }, "c").start();
        Thread.sleep(1000);
        System.out.println("开端...");
        awaitSignal.lock();
        try {
            a_condition.signal();  // 首先唤醒a线程
        } finally {
            awaitSignal.unlock();
        }
    }
​
}
​
class AwaitSignal extends ReentrantLock {
    private final int loopNumber;
​
    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }
    /**
     * print
     * @param str 打印的内容
     * @param current 进入那间休息室
     * @param next 下一间休息室
     */
    public void print(String str, Condition current, Condition next) {
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                try {
                    current.await();
                    System.out.print(str);
                    // 唤醒下一间休息室
                    next.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                unlock();
            }
        }
    }
}
​

LockSupport的park/unpark完成

/**
 * Description: 线程1输出a5次,线程2输出b5次,线程3输出c5次。现在要求输出 abcabcabcabcabc怎样完成
 */
@Slf4j
public class TestParkUnpark {
    static Thread a;
    static Thread b;
    static Thread c;
​
    public static void main(String[] args) {
        ParkUnpark parkUnpark = new ParkUnpark(5);
​
        a = new Thread(() -> {
            parkUnpark.print("a", b);
        }, "a");
        b = new Thread(() -> {
            parkUnpark.print("b", c);
        }, "b");
        c = new Thread(() -> {
            parkUnpark.print("c", a);
        }, "c");
        a.start();
        b.start();
        c.start();
        LockSupport.unpark(a);
​
    }
}
​
class ParkUnpark {
    private final int loopNumber;
​
    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }
    public void print(String str, Thread nextThread) {
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            System.out.print(str);
            Lockpport.unpark(nextThread);
        }
    }
}

本文深度解析了ReentrantLock各方面,以及公正锁和不公正锁完成的原理。关于Android开发技术中还有许多的核心技术进阶,想要了解更多,或许> 前往以下链接:

传送直达↓↓↓docs.qq.com/doc/DUkNRVF…

文末

  • ReentrantLock是可重入锁:线程获取锁的时分,假如现已获取锁的线程便是当时线程的话,则此线程直接再次获取成功。由于锁会被获取n次,则在开释锁的时分也要开释n次,才算开释成功。基于内部类Sync属性state(承继AQS的state)进行计算。
  • ReentrantLock完成了公正锁与非公正锁:ReentrantLock在构造函数中供给了是否公正锁的初始化办法,默许对错公正锁。非公正锁的功率要远远高于公正锁,一般咱们没有特别需求的话都是用非公正锁。ReentrantLock内部类有抽象类Sync(Sync 承继抽象类AQS),FairSync,NonfairSync(FairSync,NonfairSync承继Sync完成)。用于完成公正锁与非公正锁。