相对于synchronized,RenentrantLock有这么几个特点

可以被中断,可以设置超时时间,支持多个条件变量,可以设置成公平锁。

同时RenentrantLock和synchronized都是可重入的

一、可重入

可重入指的是如果一个线程首次获取了锁,它还可以再次获取到这个锁,举个例子来说明

public class Test7 {

    private static final Logger LOGGER = LoggerFactory.getLogger(Test7.class);

   public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    LOGGER.info("main 获取锁");
                    m2();
                } finally {
                    //释放锁
                    lock.unlock();
                }
            }
        });
        t1.start();
    }

    public static void m2(){
        lock.lock();
        try {
            LOGGER.info("m2 获取锁");
        } finally {
            lock.unlock();
        }
    }
}

上边的代码中线程t先获取到锁,然后其调用方法m2再次加锁,再次加锁可以成功,这就是可重入,一个线程已经持有锁后再次获取同一把锁。

二、可中断(打断)

public class Test7 {

    private static final Logger LOGGER = LoggerFactory.getLogger(Test7.class);

   public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    LOGGER.info("尝试获取锁");
                    lock.lockInterruptibly();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    LOGGER.info("未获取到锁,被其他线程打断了");
                    return;
                }
                //获取到锁
                LOGGER.info("获取到了锁");
            }
        });

        //主线程先获取锁
        lock.lock();
        t1.start();
        //这时t1获取不到锁,被阻塞,处于BLOCKED状态
        Thread.sleep(2000);
        //2s后打断线程t1,t1恢复运行
        LOGGER.info("主线程打断");
        t1.interrupt();
    }
}

当调用ReentrantLock对象的lockInterruptibly方法来加锁时此方法支持被打断,如果获取不到锁就会阻塞住,这时其他线程可以打断它,就会抛出InterruptedException异常并恢复运行。

注意如果t1已经获取到锁了并开始执行自己的代码,这时候其他线程再去打断t1是没有效果的

Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    LOGGER.info("尝试获取锁");
                    lock.lockInterruptibly();
                    while (true){
                        LOGGER.info("ooo");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    LOGGER.info("未获取到锁,被其他线程打断了");
                }
            }
        });

        t1.start();
        Thread.sleep(2000);
        LOGGER.info("主线程打断");
        t1.interrupt();

就像这样t1会一直循环,主线程打断也不起作用

三、设置超时时间

调用tryLock方法可以设置超时时间

public class Test7 {

    private static final Logger LOGGER = LoggerFactory.getLogger(Test7.class);

   public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean res = false;
                try {
                    LOGGER.info("尝试获取锁");
                    res = lock.tryLock(2, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if(res){
                    //成功获取到锁
                    try {
                        LOGGER.info("成功获取到锁");
                    } finally {
                        lock.unlock();
                    }
                } else{
                    LOGGER.info("未获取到锁");
                }
            }
        });
        //主线程先获取锁
        lock.lock();
        t1.start();

    }
}

t1先尝试获取锁,因为主线程持有了锁所以获取不到,t1等待2s后就会返回false表示未获取到锁。

还有一个不带参数的tryLock方法调用后如果获取不到锁就会立即返回false。

注意带参数的tryLock方法是可以被打断的,不带参数的不可以

四、设置成公平锁

ReentrantLock默认情况下和synchronized一样,都是非公平锁,多个线程进入waitset进行等待,被唤醒后会竞争锁,跟进入waitset的顺序没关系。公平锁的情况下先进入等待的就会先获取到锁。

如果创建ReentrantLock对象时传入一个true参数创建的就是公平锁

public static ReentrantLock lock = new ReentrantLock(true);

五、条件变量

使用synchronized时底层使用的是Monitor对象,调用wait方法时线程是在waitset中等待,一个Monitor对象中只有一个waitset。而使用ReentrantLock时,一个锁对象中相可以有多个waitset,条件不同可以到不同的休息室中等待,这样唤醒时就不需要像synchronized那样把waitset中的所有线程都唤醒

public class Test7 {

    private static final Logger LOGGER = LoggerFactory.getLogger(Test7.class);

    public static ReentrantLock lock = new ReentrantLock(true);

    //创建两个休息室
    public static Condition condition1 = lock.newCondition();
    public static Condition condition2 = lock.newCondition();

    public static boolean flag1 = false;
    public static boolean flag2 = false;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    while (!flag1){
                        // 没有烟不能干活
                        try {
                            LOGGER.info("没烟不能干活");
                            condition1.await();//在休息室1中等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            return;
                        }
                    }

                    LOGGER.info("有烟了可以干活");
                } finally {
                    lock.unlock();
                }
            }
        },"t1");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    while (!flag2){
                        LOGGER.info("没有外卖不能干活");
                        try {
                            condition2.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    LOGGER.info("有外卖了,可以干活");
                } finally {
                    lock.unlock();
                }
            }
        },"t2");

        t1.start();
        t2.start();

        Thread.sleep(1000);
        try {
            lock.lock();
            flag1=true;//送烟
            condition1.signal();//叫醒
        } finally {
            lock.unlock();
        }

        Thread.sleep(2000);
        try {
            lock.lock();
            flag2=true;//送外卖
            condition2.signal();
        } finally {
            lock.unlock();
        }
    }
}