ReentrantLock概述

相对于 synchronized 它具备如下特点

  1. 可中断
  2. 可以设置超时时间
  3. 可以设置为公平锁
  4. 支持多个条件变量,即对与不满足条件的线程可以放到不同的集合中等待

与 synchronized 一样,都支持可重入

基本语法

// 获取锁
reentrantLock.lock();
try {
 // 临界区
} finally {
 // 释放锁
 reentrantLock.unlock();
}

可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

  static  ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) {

        try {
            reentrantLock.lock();
            m1();
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void m1() {
        try {
            reentrantLock.lock();
            m2();
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void m2() {
        try {
            reentrantLock.lock();
        } finally {
            reentrantLock.unlock();
        }
    }

可打断

直接看例子:

这样有效避免死锁的发生。

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();

        Thread t1 = new Thread(() -> {
            try {
                /**
                 *
                 * 注意这里使用的是lockInterruptibly方法,如果使用lock.lock()方法,那么这里
                 * 等待的时候是不可以被打断的
                 */
                log.info("尝试获取锁");
                reentrantLock.lockInterruptibly(); //被其它使用interrupt的线程打断
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.info("获取锁失败了");
                // 这里如果出了错不要再往下执行了
                return;
            }

            try {
                log.info("获取到锁了没有被打断!");
            } finally {
                reentrantLock.unlock();
            }
        }, "t1");
        reentrantLock.lock();
        t1.start();
        Thread.sleep(222);
        // 打断线程thread,原本它的状态是在等待锁的,我们在它等待锁的时候打断了,不让它继续等待了
        t1.interrupt();
        log.info("主线程执行结束了 ");
    }

锁超时

直接看例子线程获取锁指定等待时间超过了就会别打断:

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();


        Thread t1 = new Thread(() -> {
            try {
                      //lock.tryLock指定时间获取不到锁就会释放,lock.lock获取不到锁会无限等待
                if (!lock.tryLock(2, TimeUnit.SECONDS)) {//false
                    log.info("加锁失败了!");
                    // 这里如果出了错不要再往下执行了
                    return;
                }
            } catch (Exception e) {
                log.info("被打断啦");
                e.printStackTrace();
                //执行到这里失败就不要继续执行了
                return;
            }

            try {
                log.info("执行完啦,获取到了锁,没被打断");

            } finally {
                lock.unlock();
            }

        }, "t1");
        log.info("主线程获取锁");
        lock.lock();
        t1.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("主线程释放锁");
        lock.unlock();

        log.info("线程运行结束");
    }

使用锁超时解决哲学家就餐死锁问题:

@Slf4j
public class RepastTest {

    public static void main(String[] args) {
        Chopstick2 c1 = new Chopstick2("1");
        Chopstick2 c2 = new Chopstick2("2");
        Chopstick2 c3 = new Chopstick2("3");
        Chopstick2 c4 = new Chopstick2("4");
        Chopstick2 c5 = new Chopstick2("5");
        new Philosopher2("苏格拉底", c1, c2).start();
        new Philosopher2("柏拉图", c2, c3).start();
        new Philosopher2("亚里士多德", c3, c4).start();
        new Philosopher2("赫拉克利特", c4, c5).start();
        new Philosopher2("阿基米德", c5, c1).start();
    }

}

@Slf4j(topic = "Philosopher")
class Philosopher2 extends Thread{
    Chopstick2 left;
    Chopstick2 right;
    public Philosopher2(String name, Chopstick2 left, Chopstick2 right) {
        super(name);
        this.left = left;
        this.right = right;
    }
    private void eat() {
        log.debug("eating...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                if (left.tryLock(2, TimeUnit.SECONDS)){
                    try {
                        if (right.tryLock(2, TimeUnit.SECONDS)){
                            try {
                                eat();
                            }finally {
                                right.unlock();
                            }
                        }
                    }finally {
                        left.unlock();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        }
    }
}

class Chopstick2 extends ReentrantLock {
    private String name ;

    public Chopstick2(String name) {
        this.name = name;
    }


    @Override
    public String toString() {
        return "Chopstick{" +
                "name='" + name + '\'' +
                '}';
    }
}

公平锁

synchronized锁中,在entrylist等待的锁在竞争时不是按照先到先得来获取锁的,所以说synchronized锁时不公平的;ReentranLock锁默认是不公平的,但是可以通过设置实现公平锁。本意是为了解决之前提到的饥饿问题,但是公平锁一般没有必要,会降低并发度,使用trylock也可以实现。

设置:只要把构造器设置为true即可

  源码:  /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比

  1. synchronized 是那些不满足条件的线程都在一间休息室等消息
  2. 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤

使用要点:

  1. await 前需要获得锁
  2. await 执行后,会释放锁,进入 conditionObject 等待
  3. await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁,执行唤醒的线程爷必须先获得锁
  4. 竞争 lock 锁成功后,从 await 后继续执行
static Lock lock = new ReentrantLock();
    static Condition waitCigaretteQueue = lock.newCondition();
    static Condition waitBreakfastQueue = lock.newCondition();
    static volatile boolean hasCigarette = false;
    static volatile boolean hasBreakfast = false;


    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                lock.lock();
                while (!hasCigarette) {
                    try {
                        waitBreakfastQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("等到他的烟。");
            } finally {
                lock.unlock();
            }

        }, "等烟线程");
        thread.start();

        Thread thread2 = new Thread(() -> {

            try {
                lock.lock();
                while (!hasBreakfast) {
                    try {
                        waitCigaretteQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.info("等到早餐!");

                }
            } catch (Exception e) {
                e.printStackTrace();
                lock.unlock();
            }
        }, "等早餐线程");

        thread2.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sendCigarette();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        sendBreakfast();

    }

    public static void sendCigarette() {
        try {
            lock.lock();
            log.info("烟来了");
            hasCigarette = true;
            waitCigaretteQueue.signal();
        } finally {
            lock.unlock();
        }
    }

    public static void sendBreakfast() {
        lock.lock();
        try {
            log.info("送早餐来了");
            hasBreakfast = true;
            waitBreakfastQueue.signal();
        } finally {
            lock.unlock();
        }
    }

同步模式之顺序控制

  1. 固定运行顺序,比如,必须先 2 后 1 打印

    1. wait notify 版
    public class OrderLock {
        // 用来同步的对象
        static Object obj = new Object();
    
        // t2 运行标记, 代表 t2 是否执行
        static boolean t2runed = false;
    
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                synchronized (obj) {
                    while (!t2runed) {
    
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(1);
                }
    
            }, "t1");
    
            Thread t2 = new Thread(() -> {
    
                synchronized (obj) {
                    t2runed = true;
                    obj.notify();
                }
                System.out.println(2);
    
            }, "t2");
    
            t1.start();
            t2.start();
    
        }
    }
    
    1. Park Unpark 版
    /**
     * wait 和 notify的缺点
     * 首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了『运行标记』来判断该不该
     * wait
     * 第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决
     * 此问题
     * 最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为『同步对象』上的等待线程可能不止一个
     * 
     * park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』,
     * 不需要『同步对象』和『运行标记』,不存在一个多个线程同时等待一个对象锁的现象,因为每个线程调用park的结果是等待它自己线程的upark来解锁
     */
    public class Test36 {
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                try { Thread.sleep(1000); } catch (InterruptedException e) { }
                // 当没有『许可』时,当前线程暂停运行;有『许可』时,用掉这个『许可』,当前线程恢复运行
                LockSupport.park();
                System.out.println("1");
            });
            Thread t2 = new Thread(() -> {
                System.out.println("2");
                // 给线程 t1 发放『许可』(多次连续调用 unpark 只会发放一个『许可』)
                LockSupport.unpark(t1);
            });
            t1.start();
            t2.start();
        }
    }
    

    使用notify和wait实现循环打印abcabc

    public class WaitNotify {
        /**
         * 使用notify和wait实现循环打印abcabc
         * 交替打印ABC
         * 输出内容:    等待标记:     下一个标记:
         * A             1            2
         * B             2            3
         * C             3            1
         */
        public static void main(String[] args) {
            WaitNotifyDemo waitNotify = new WaitNotifyDemo(1, 15);
            new Thread(() -> {
                waitNotify.print("a", 1, 2);
            }, "线程一").start();
            new Thread(() -> {
                waitNotify.print("b", 2, 3);
            }, "线程二").start();
            new Thread(() -> {
                waitNotify.print("c", 3, 1);
            }, "线程三").start();
    
        }
    }
    class WaitNotifyDemo {
        int flag;
        int loopNumber;
        public WaitNotifyDemo(int flag, int loopNumber) {
            this.flag = flag;
            this.loopNumber = loopNumber;
        }
        /**
         * @param str      要打印的内容
         * @param flag     线程的打印标记
         * @param nextFlag 下一个打印标记
         */
        public void print(String str, int flag, int nextFlag) {
            for (int i = 0; i < loopNumber; i++) {
                synchronized (this) {
                    while (this.flag != flag) {
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //标记相同则打印
                    System.out.println(str);
                    this.flag = nextFlag;
                    // 修改了打印标记,唤醒其它线程让他们抢啦!
                    this.notifyAll();
                }
            }
        }
    }
    
  2. 交替输出,线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现

    1. wait notify 版 Test37.java
    2. Lock 条件变量版
    public class AwaitSignalDemo {
        public static void main(String[] args) {
            AwaitSignal awaitSignal = new AwaitSignal(15);
            Condition aCondition = awaitSignal.newCondition();
            Condition bCondition = awaitSignal.newCondition();
            Condition cCondition = awaitSignal.newCondition();
    
    
            new Thread(()->{
                awaitSignal.print("a",aCondition,bCondition);
            },"线程一").start();
            new Thread(()->{
                awaitSignal.print("b",bCondition,cCondition);
            },"线程二").start();
            new Thread(()->{
                awaitSignal.print("c",cCondition,aCondition);
            },"线程三").start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            awaitSignal.lock();
            try {
                aCondition.signal();
            }finally {
                awaitSignal.unlock();
            }
        }
    }
    
    class AwaitSignal extends ReentrantLock {
        int loopNUmber;
    
        public AwaitSignal(int loopNUmber) {
            this.loopNUmber = loopNUmber;
        }
    
        public void print(String str, Condition current, Condition next) {
            for (int i = 0; i < loopNUmber; i++) {
                this.lock();
                try {
                    try {
                        current.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(str);
                    next.signal();
                } finally {
                    this.unlock();
                }
            }
    
        }
    }
    
    1. Park Unpark 版
    public class Test39 {
    
        static Thread thread2 ;
        static Thread thread1;
        static Thread thread3;
        
        public static void main(String[] args) {
    
            ParkUnpark parkUnpark = new ParkUnpark(15);
    
    
            thread1 = new Thread(() -> {
                parkUnpark.print("a",thread2);
            }, "线程一");
            thread2 = new Thread(() -> {
                parkUnpark.print("b",thread3);
            }, "线程二");
            thread3 = new Thread(() -> {
                parkUnpark.print("c",thread1);
            }, "线程三");
            
            thread1.start();
            thread2.start();
            thread3.start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            LockSupport.unpark(thread1);
        }
        
        
    }
    
    
    class ParkUnpark{
        int loopNumber;
    
        public ParkUnpark(int loopNumber) {
            this.loopNumber = loopNumber;
        }
        
        public void print(String str , Thread next ){
            for (int i=0;i<loopNumber;i++){
                LockSupport.park();
                System.out.print(str);
                LockSupport.unpark(next);
            }
        }
    }
    
posted @ 2021-03-08 23:35  金融融融融果果  阅读(151)  评论(2)    收藏  举报