8.ReentrantLock
          目录
          
        
        
      ReentrantLock
简介
相对于 synchronized 它具备如下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
==与 synchronized 一样,都支持可重入==
基础语法:
// 获取锁
reentrantLock.lock();
try {
    // 临界区
} finally {
    // 释放锁
    reentrantLock.unlock();
}可重入
@Slf4j(topic = "c.ReentrantLockTest")
public class ReentrantLockTest {
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        method1();
    }
    public static void method1(){
        lock.lock();
        try{
            log.debug("execute method1");
            method2();
        }finally {
            lock.unlock();
        }
    }
    public static void method2(){
        lock.lock();
        try{
            log.debug("execute method2");
            method3();
        }finally {
            lock.unlock();
        }
    }
    public static void method3(){
        lock.lock();
        try{
            log.debug("execute method3");
        }finally {
            lock.unlock();
        }
    }
}输出:
15:39:20.988 c.ReentrantLockTest [main] - execute method1
15:39:20.990 c.ReentrantLockTest [main] - execute method2
15:39:20.990 c.ReentrantLockTest [main] - execute method3
Process finished with exit code 0可打断 lock.lockInterruptibly()
@Slf4j(topic = "c.LockInterruptiblyTest")
public class LockInterruptiblyTest {
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            try {
                //没有竞争就会获取锁
                //有竞争就进入阻塞队列等待,但可以被打断
                lock.lockInterruptibly();
                //lock.lock(); //不可打断
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("等锁的过程中被打断");
                return;
            }
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
            SleepUtil.sleep(1);
            log.debug("执行打断");
            t1.interrupt();
        } finally {
            lock.unlock();
        }
    }
}输出:
15:40:30.630 c.LockInterruptiblyTest [main] - 获得了锁
15:40:30.633 c.LockInterruptiblyTest [t1] - 启动...
15:40:31.639 c.LockInterruptiblyTest [main] - 执行打断
15:40:31.640 c.LockInterruptiblyTest [t1] - 等锁的过程中被打断
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at cn.com.wuhm.juc.three.n4.LockInterruptiblyTest.lambda$main$0(LockInterruptiblyTest.java:24)
	at java.lang.Thread.run(Thread.java:750)
Process finished with exit code 0如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断
@Slf4j(topic = "c.LockInterruptiblyTest")
public class LockInterruptiblyTest {
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("启动...");
//            try {
//                //没有竞争就会获取锁
//                //有竞争就进入阻塞队列等待,但可以被打断
//                lock.lockInterruptibly();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//                log.debug("等锁的过程中被打断");
//                return;
//            }
            
            //不可打断
            lock.lock(); 
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
            SleepUtil.sleep(1);
            log.debug("执行打断");
            t1.interrupt();
        } finally {
            lock.unlock();
        }
    }
}输出:
15:41:41.069 c.LockInterruptiblyTest [main] - 获得了锁
15:41:41.071 c.LockInterruptiblyTest [t1] - 启动...
15:41:42.073 c.LockInterruptiblyTest [main] - 执行打断
15:41:42.073 c.LockInterruptiblyTest [t1] - 获得了锁
Process finished with exit code 0锁(可设置)超时 lock.tryLock()
立刻返回结果:
@Slf4j(topic = "c.TestTryLock")
public class TestTryLock {
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("t1开始尝试获取锁");
            if(!lock.tryLock()){
                log.debug("未获取到锁");
                return;
            }
            try{
                log.debug("获取到锁");
            }finally {
                lock.unlock();
            }
        }, "t1");
        log.debug("获取锁");
        lock.lock();
        t1.start();
        SleepUtil.sleep(1);
        lock.unlock();
    }
}输出:
17:39:53.222 c.TestTryLock [main] - 获取锁
17:39:53.224 c.TestTryLock [t1] - t1开始尝试获取锁
17:39:53.224 c.TestTryLock [t1] - 未获取到锁
Process finished with exit code 0尝试等待多少时间后返回结果:
@Slf4j(topic = "c.TestTryLock")
public class TestTryLock {
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("t1开始尝试获取锁");
            try {
                if(!lock.tryLock(2, TimeUnit.SECONDS)){
                    log.debug("未获取到锁");
                    return;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            try{
                log.debug("获取到锁");
            }finally {
                lock.unlock();
            }
        }, "t1");
        log.debug("获取锁");
        lock.lock();
        t1.start();
        SleepUtil.sleep(1);
        lock.unlock();
    }
}输出:
17:38:27.472 c.TestTryLock [main] - 获取锁
17:38:27.474 c.TestTryLock [t1] - t1开始尝试获取锁
17:38:28.478 c.TestTryLock [t1] - 获取到锁
Process finished with exit code 0使用 ReentrantLock 的 tryLock() 解决哲学家就餐问题
代码改造:
筷子类继承 ReentrantLock
public class Chopstick extends ReentrantLock {
    private String name;
    public Chopstick(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}哲学家类 run方法改造
@Override
public void run() {
    while (true){
        // 使用 ReentrantLock 改造
        if(left.tryLock()){
            try{
                if(right.tryLock()){
                    try {
                        eat();
                    }finally {
                        right.unlock();
                    }
                }
            }finally {
                left.unlock();
            }
        }
    }
}测试结果:
。。。。。。
17:44:42.255 c.Philosopher [苏格拉底] - eating......
17:44:42.255 c.Philosopher [亚里士多德] - eating......
17:44:43.259 c.Philosopher [苏格拉底] - eating......
17:44:43.259 c.Philosopher [亚里士多德] - eating......
17:44:44.263 c.Philosopher [亚里士多德] - eating......
17:44:44.263 c.Philosopher [苏格拉底] - eating......
17:44:45.267 c.Philosopher [亚里士多德] - eating......
17:44:45.267 c.Philosopher [阿基米德] - eating......
17:44:46.272 c.Philosopher [阿基米德] - eating......
17:44:46.272 c.Philosopher [亚里士多德] - eating......
17:44:47.276 c.Philosopher [亚里士多德] - eating......
17:44:47.276 c.Philosopher [苏格拉底] - eating......
。。。。。。不再产生死锁和饥饿的问题
(多个)条件变量
synchronized也有条件变量,synchronized是操作系统层面实现的,而ReentrantLock是java层面实现的,但是其实现 的逻辑和思路大致相同,操作系统的Monitor由,waitSet,EntryList,Owner,三部分组成,其中waitSet等于ReentrantLock的条件变量。ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
- synchronized 是那些不满足条件的线程都在一间休息室等消息
- 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
使用要点:
- await前需要获得锁
- await执行后,会释放锁,进入- conditionObject等待
- await的线程被唤醒(或打断、或超时)去重新竞争 lock 锁
- 竞争 lock 锁成功后,从 await 后继续执行
@Slf4j(topic = "c.Test10")
public class Test10 {
    static ReentrantLock lock = new ReentrantLock();
    // 定义两个条件变量
    static Condition smokeWaitSetCondition = lock.newCondition();
    static Condition takeoutWaitSetCondition = lock.newCondition();
    static volatile boolean hasSmoke = false;
    static volatile boolean hasTakeout = false;
    public static void main(String[] args) {
        new Thread(()->{
            lock.lock();
            try {
                while (!hasSmoke) {
                    log.debug("没有烟,休息一会");
                    try {
                        smokeWaitSetCondition.await();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("来了一口烟,神清气爽,继续工作");
            }finally {
                lock.unlock();
            }
        }, "小明").start();
        new Thread(()->{
            lock.lock();
            try {
                while (!hasSmoke) {
                    log.debug("外卖还没到,休息一会");
                    try {
                        takeoutWaitSetCondition.await();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("来了一口饭,横扫饥饿,继续工作");
            }finally {
                lock.unlock();
            }
        }, "小强").start();
        SleepUtil.sleep(1);
        sendSmoke();
        sendTakeout();
    }
    private static void sendSmoke(){
        lock.lock();
        try{
            log.debug("送烟来了");
            hasSmoke = true;
            smokeWaitSetCondition.signal();
        }finally {
            lock.unlock();
        }
    }
    private static void sendTakeout(){
        lock.lock();
        try{
            log.debug("外卖来了");
            hasTakeout = true;
            takeoutWaitSetCondition.signal();
        }finally {
            lock.unlock();
        }
    }
}输出:
14:26:50.387 c.Test10 [小明] - 没有烟,休息一会
14:26:50.389 c.Test10 [小强] - 外卖还没到,休息一会
14:26:51.391 c.Test10 [main] - 送烟来了
14:26:51.391 c.Test10 [main] - 外卖来了
14:26:51.391 c.Test10 [小明] - 来了一口烟,神清气爽,继续工作
14:26:51.391 c.Test10 [小强] - 来了一口饭,横扫饥饿,继续工作
Process finished with exit code 0运用案例
控制线程执行顺序
需要控制线程2总是比线程1先执行
方式一:wait & notify
@Slf4j(topic = "c.SequenceControl1")
public class SequenceControl1 {
    static Object lock = new Object();
    static boolean running = false;
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock){
                while (!running){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.debug("t1 start execute");
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            synchronized (lock){
                log.debug("t2 start execute");
                running = true;
                lock.notifyAll();
            }
        }, "t2");
        SleepUtil.sleep(0.1);
        t1.start();
        t2.start();
    }
}局限性:
- 首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了运行标记来判断该不该wait
- 第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决此问题
- 最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为同步对象上的等待线程可能不止一个
方式二:park & unpark (推荐)
park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。
并且是以线程为单位进行『暂停』和『恢复』,不需要『同步对象』和『运行标记』
@Slf4j(topic = "c.SequenceControl2")
public class SequenceControl2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
            log.debug("t1 start execute");
        }, "t1");
        Thread t2 = new Thread(() -> {
            log.debug("t2 start execute");
            LockSupport.unpark(t1);
        }, "t2");
        SleepUtil.sleep(0.1);
        t1.start();
        t2.start();
    }
}输出:
15:05:47.893 c.SequenceControl2 [t2] - t2 start execute
15:05:47.895 c.SequenceControl2 [t1] - t1 start execute
Process finished with exit code 0==注意使用park和unpark与输出语句的先后顺序==
交替打印
场景:线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现
方式一:wait & notifyAll
解析:之前控制顺序是通过标志位,现在是多个线程顺序控制,我们可以通过1,2,3……这样的数值型表示线程执行的循序
@Slf4j(topic = "c.AlternatePrint1")
public class AlternatePrint1 {
    public static void main(String[] args) {
        SyncWaitNotify syncWaitNotify = new SyncWaitNotify(1, 5);
        new Thread(()->{
            syncWaitNotify.print(1, 2, "a");
        }, "t1").start();
        new Thread(()->{
            syncWaitNotify.print(2, 3, "b");
        }, "t2").start();
        new Thread(()->{
            syncWaitNotify.print(3, 1, "c");
        }, "t3").start();
    }
}
class SyncWaitNotify{
    /**
     * 线程的标记:线程1-1;线程2-2;线程3-3
     */
    private int flag;
    /**
     * 循环次数
     */
    private int loopNumber;
    public SyncWaitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }
    public void print(int currentFlag, int nextFlag, String ch){
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this){
                // 如果当前标记不是这个线程的,进入阻塞队列
                while (this.flag != currentFlag){
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                // 如果相等输出字符
                System.out.print(ch);
                // 将标记为移向下一个线程标志位
                this.flag = nextFlag;
                // 唤醒阻塞队列所有线程
                this.notifyAll();
            }
        }
    }
}输出:
abcabcabcabcabc方式二:ReentrantLock
@Slf4j(topic = "c.AlternatePrint2")
public class AlternatePrint2 {
    public static void main(String[] args) {
        SyncAWaitSignal syncAWaitSignal = new SyncAWaitSignal(5);
        Condition conditionA = syncAWaitSignal.newCondition();
        Condition conditionB = syncAWaitSignal.newCondition();
        Condition conditionC = syncAWaitSignal.newCondition();
        new Thread(()->{
            syncAWaitSignal.print(conditionA, conditionB, "a");
        }, "t1").start();
        new Thread(()->{
            syncAWaitSignal.print(conditionB, conditionC, "b");
        }, "t2").start();
        new Thread(()->{
            syncAWaitSignal.print(conditionC, conditionA, "c");
        }, "t3").start();
        // 主线程需要主动唤醒第一个线程工作
        SleepUtil.sleep(0.2);
        syncAWaitSignal.lock();
        try{
            conditionA.signal();
        }finally {
            syncAWaitSignal.unlock();
        }
    }
}
class SyncAWaitSignal extends ReentrantLock {
    /**
     * 循环次数
     */
    private int loopNumber;
    public SyncAWaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }
    public void print(Condition currentCondition, Condition nextCondition, String ch){
        for (int i = 0; i < loopNumber; i++) {
            // 刚开始让所有线程进入阻塞状态
            this.lock();
            try{
                try {
                    currentCondition.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.print(ch);
                // 唤醒下一个线程
                nextCondition.signalAll();
            }finally {
                this.unlock();
            }
        }
    }
}输出:
abcabcabcabcabc方式三:park & unpark
@Slf4j(topic = "c.AlternatePrint3")
public class AlternatePrint3 {
    static Thread t1;
    static Thread t2;
    static Thread t3;
    public static void main(String[] args) {
        SyncParkUnPark syncParkUnPark = new SyncParkUnPark(5);
        t1 = new Thread(() -> {
            syncParkUnPark.print(t2, "a");
        }, "t1");
        t2 = new Thread(()->{
            syncParkUnPark.print(t3, "b");
        }, "t2");
        t3 = new Thread(()->{
            syncParkUnPark.print(t1, "c");
        }, "t3");
        t1.start();
        t2.start();
        t3.start();
        // 主线程需要主动唤醒第一个线程工作
        SleepUtil.sleep(0.2);
        LockSupport.unpark(t1);
    }
}
class SyncParkUnPark extends ReentrantLock {
    /**
     * 循环次数
     */
    private int loopNumber;
    public SyncParkUnPark(int loopNumber) {
        this.loopNumber = loopNumber;
    }
    public void print(Thread nextThread, String ch){
        for (int i = 0; i < loopNumber; i++) {
            // 刚开始让所有线程进入阻塞状态
            LockSupport.park();
            System.out.print(ch);
            LockSupport.unpark(nextThread);
        }
    }
}输出:
abcabcabcabcabc