1.ReentrantLock可重入锁
1.1.简介
1>.可重入是指同一个线程如果首次获得了这把锁,那么由于它是这把锁的拥有者,因此该线程有权利(/优先)再次获取这把锁;如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住;
ReentrantLock底层也是基于Monitor对象实现的,只不过它是在Java级别实现的(CAS+AQS),而synchronized是在JVM层面基于Monitor对象实现的线程安全/线程同步的机制,源码是C++的源码;
2>.ReentrantLock相对于Synchronized它具备如下特点:
①.可中断;
②.可以设置超时时间;
③.可以设置为公平锁(/先进先出,不会出现线程饥饿现象);
④.支持多个条件变量;
它与synchronized一样,都支持可重入!
1.2.基本语法
// 获取锁
ReentrantLock reentrantLock = new ReentrantLock(true);
// 加锁
reentrantLock.lock();
try {
  // 临界区
} finally {
  // 释放锁(这一步很重要!!!)
  reentrantLock.unlock();
}
Synchronized是在关键字这个级别保护临界区,而ReentrantLock是在对象的级别保护临界区,因此必须要先创建一个ReentrantLock对象;
1.3.可重入案例
@Slf4j
public class TestReentrantLockDemo1 {
    //定义ReentrantLock对象
    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) {
        //加锁/获取锁,保护临界区
        reentrantLock.lock();
        try {
            log.info("进入mian方法");
            //当前线程获取到锁,但是没有释放,再去调用其他同步代码
            m1();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            log.info("main方法释放锁{}",reentrantLock);
            //解锁,会唤醒其他等待/阻塞中的线程
            //注意这个操作必须要和之前的lock()加锁操作成对出现!!!
            reentrantLock.unlock();
        }
    }
    private static void m1() {
        //加锁/获取锁,保护临界区
        reentrantLock.lock();
        try {
            log.info("进入m1方法");
            m2();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            log.info("m1方法释放锁{}",reentrantLock);
            //解锁,会唤醒其他等待/阻塞中的线程
            //注意这个操作必须要和之前的lock()加锁操作成对出现!!!
            reentrantLock.unlock();
        }
    }
    private static void m2() {
        //加锁/获取锁,保护临界区
        reentrantLock.lock();
        try {
            log.info("进入m2方法");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            log.info("m2方法释放锁{}",reentrantLock);
            //解锁,会唤醒其他等待/阻塞中的线程
            //注意这个操作必须要和之前的lock()加锁操作成对出现!!!
            reentrantLock.unlock();
        }
    }
}

1.4.可打断案例
@Slf4j
public class TestReentrantLockDemo2 {
    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                log.info("线程t1尝试获取锁...");
                //加锁并且指定当线程是可以被打断的
                //即如果当前没有其他线程竞争锁,那么当前线程正常获取到锁,相当于执行了lock()操作;如果有其他线程竞争锁,而当前当前线程没有抢到锁,那么当前线程就会进入到阻塞队列中等待,但是这个线程的等待状态是可以被其他线程用interrupt()方法打断/终止的!
                //可以避免线程无限制等待/阻塞,造成死锁!
                reentrantLock.lockInterruptibly();
            } catch (InterruptedException e) {
                log.info("没有获得锁,线程t1被打断,运行结束!");
                e.printStackTrace();
                return;
            }
            try {
                //获取到锁,执行业务代码
                log.info("线程t1获取到锁,执行业务代码...");
            } finally {
                log.info("线程t1释放锁");
                //解锁,会唤醒其他等待/阻塞中的线程
                reentrantLock.unlock();
            }
        }, "t1");
        //main线程获取锁
        reentrantLock.lock();
        try {
            log.info("main线程获取到锁...");
            t1.start();
            //main线程获取到锁,睡眠1s,但是没有释放锁,也就是说其他线程无法获取到当前这把锁而进入到阻塞队列中等待
            Thread.sleep(1000);
            //打断其他线程的等待状态
            t1.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            log.info("main线程释放锁!");
            //解锁,会唤醒其他等待/阻塞中的线程
            reentrantLock.unlock();
        }
    }
}

1.5.锁超时案例
@Slf4j
public class TestReentrantLockDemo3 {
    //锁对象
    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.info("线程尝试获取锁...");
            //立刻返回线程获取锁的结果,不会等待!!!
            //if (!reentrantLock.tryLock()) {
            //    log.info("获取不到锁,执行结束!");
            //    return;
            //}
            try {
                //如果线程获取到锁,可以立刻返回结果!
                //如果线程没有获取到锁,它会等待一定的时间,在等待期间会不停尝试获取锁
                //如果等待期间提前获取到锁,那么提前结束等待,返回获取到锁的结果,即返回true;
                //如果等待时间到达仍然没有获取到锁,返回获取不到锁的结果,即返回false;
                //该方法也支持当前线程某种状态被打断!!
                if (!reentrantLock.tryLock(2, TimeUnit.SECONDS)){
                    log.info("获取不到锁,执行结束!");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.info("没有获取到锁,线程被打断,执行结束!");
                return;
            }
            try {
                log.info("获取到锁,执行业务代码");
            } finally {
                log.info("线程t1执行完业务代码,释放锁!");
                reentrantLock.unlock();
            }
        }, "t1");
        //main线程获取锁
        log.info("main主线程尝试获取锁");
        reentrantLock.lock();
        try {
            log.info("main线程获取到锁,执行业务代码...");
        } finally {
            log.info("main线程执行完业务代码,释放锁!");
            reentrantLock.unlock();
        }
        t1.start();
        //打断t1线程的等待状态
        //t1.interrupt();
    }
}

1.6.公平锁
1>.ReentrantLock默认是不公平的,可以在创建ReentrantLock对象的时候在构造函数中指定一个"true"让它变成公平锁;(公平锁可以用来解决线程饥饿问题,但是一般没有必要,会降低并发度);
 
 2>.Synchronized是非公平锁!
1.7.ReentrantLock条件变量
1.7.1.概述
1>.Synchronized中也有条件变量,就是之前的waitSet休息室,当获取锁的线程由于条件不满足时进入waitSet等待;
2>.ReentrantLock的条件变量类似Synchronized条件变量,但是它比synchronized强大之处在于,它是支持多个条件变量的,这就好比:
①.synchronized是那些不满足条件的线程都在一间休息室等消息;
②.而ReentrantLock支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按相应的休息室来唤醒;
3>.条件变量API使用要点
①.await()执行前需要获得锁;
②.await()执行后会释放锁,进入某个conditionObject等待;
③.await()的线程被唤醒(或打断、或超时)取重新竞争lock锁;
④.竞争lock锁成功后,从await()方法后(/位置)继续执行;
1.7.2.案例
@Slf4j
public class TestConditionDemo1 {
    //锁对象
    static ReentrantLock reentrantLock = new ReentrantLock();
    //条件对象,一个锁对象可以对应多个条件对象,可以理解为休息室
    static Condition waitCigaretteSet = reentrantLock.newCondition();
    static Condition waitbreakfastSet = reentrantLock.newCondition();
    //共享变量
    static volatile boolean hasCigrette = false;
    static volatile boolean hasBreakfast = false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            reentrantLock.lock();
            try {
                log.info("有烟没?{}", hasCigrette);
                while (!hasCigrette) {
                    log.info("没烟,先歇会...");
                    //获取锁的线程由于某些条件不满足,导致无法继续运行,而进入等待状态,可以理解为进入某个休息室内等待
                    //执行await(),会释放锁(唤醒其他处于阻塞状态的线程竞争锁)
                    //该方法可以让处于某种状态的线程被打断
                    waitCigaretteSet.await();
                }
                //正常干活
                log.info("有烟没?{}", hasCigrette);
                if (hasCigrette) {
                    log.info("有烟,可以开始干活...");
                } else {
                    log.info("没烟,没有干成活!");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        }, "小南").start();
        new Thread(() -> {
            reentrantLock.lock();
            try {
                log.info("外卖到了没?{}", hasBreakfast);
                while (!hasBreakfast) {
                    log.info("外卖没到,先歇会...");
                    waitbreakfastSet.await();
                }
                log.info("外卖到了没?{}", hasBreakfast);
                if (hasBreakfast) {
                    log.info("外卖到了,可以干活...");
                } else {
                    log.info("外卖没到,没干成活!");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        }, "小女").start();
        Thread.sleep(1000);
        new Thread(() -> {
            reentrantLock.lock();
            try {
                log.info("外卖到了...");
                hasBreakfast = true;
                //唤醒对应休息室里面的线程
                waitbreakfastSet.signal();
            } finally {
                reentrantLock.unlock();
            }
        }, "送外卖").start();
        Thread.sleep(1000);
        new Thread(() -> {
            reentrantLock.lock();
            try {
                log.info("烟到了...");
                hasCigrette = true;
                //唤醒对应休息室里面的线程
                waitCigaretteSet.signal();
            } finally {
                reentrantLock.unlock();
            }
        }, "送烟").start();
    }
}




















