04、JUC源码分析:ReentrantLock锁基础篇

JUC-AQS原理篇
JUC-AQS源码篇
JUC-AQS的Condition之await和signal源码解析

JUC-CountDownLatch基础篇
JUC-CountDownLatch源码分析
JUC-Semaphore基础篇
JUC-Semaphore源码分析
JUC-ReentrantReadWriteLock锁基础篇
JUC-ReentrantReadWriteLock锁源码分析
JUC-ReentrantLock锁基础篇
JUC-ReentrantLock锁源码分析
JUC-CyclicBarrier基础篇
JUC-CyclicBarrier源码分析

文章目录

  • 1.概念
  • 2.常用方法
  • 3 条件变量 Condition详解
  • 4 通过代码举例来说明以上特性
    • 1.基本用法
  • 2可重入
  • 3.响应中断

1.概念

ReentrantLock是一种可重入的独占锁(互斥锁)。
独占锁: 一个线程获取()了这把锁,其他的线程则不能获取。
可重入:指的是线程A获取了这把锁,在线程A释放锁之前,线程A本身可以继续来获取这把锁。比如线程A调了了lock()方法获取了这把锁,在线程A调用unlock()方法之前,线程A本身继续调用lock()方法并不会阻塞住。重入锁的设计目的是为了解决死锁的问题
ReentrantLock常常与Synchronized做对比,它们之间的对比如下:

1、 ReentrantLock和Synchronized都是独占锁,但是Synchronized的加解锁的过程是自动进行的,而ReentrantLock的加解锁的过程需要手动调用lock(),unlock()方法;
2、 ReentrantLock和Synchronized都是可重入但是Synchronized的加解锁过程是自动进行的,所以不必担心锁没有被释放的问题但是ReentrantLock的加解锁的过程需要手动显示调用,所以ReentrantLock的加锁的次数与解锁的次数必须一致,否则此线程会一直持有这把锁,会导致其他的线程无法获取到这把锁;
3、 Synchronized如果获取不到锁,就会一直在等待(不响应中断),也就是说不会被打断而ReentrantLock支持超时或者响应中断的也支持尝试去获取一把锁的,获取到就返回true,获取不到就返回false,并不会阻塞,比较灵活;
4、 ReentrantLock有公平锁与非公平锁两种方式,而Synchronized只支持非公平锁这一种方式;
当等待队列中已经有线程在排队等待获取锁时,此时刚好来了一个新的线程也来抢这把锁.那么此时这把锁应该是给谁?
公平锁:给等待队列中第一个排队的线程,新来的线程没有资格抢,只能去等待队列中排队。
非公平锁:让新来的线程跟等待队列中第一个排队的线程进行抢,谁抢到了就是谁的。
5、 实现机制:ReentrantLock是依赖AQS来实现的,而Synchronized是依赖监视器模式来实现的;
6、 条件队列:ReentrantLock可以关联多个条件队列,而Synchronized只关联一个条件队列;

2.常用方法

ReentrantLock提供的一些常用的方法有:

方法 说明
lock() 获取这把锁,如果获取不到,则当前线程进入同步队列中阻塞。直到其他的线程释放了这把锁
unlock() 当前线程释放这把锁,如果state=0(有可能当前线程自己重复多次获取了这把锁,unlock调用一次,state值可能不为0),则唤醒同步队列中等待的阻塞线程
lockInterruptibly() 获取这把锁,如果获取不到,则当前线程进入同步队列中阻塞,直到其他的线程释放了这把锁。如果当前线程在阻塞的过程中,别其他的线程中断了,则会抛出中断异常。
tryLock() 尝试获取这把锁,如果获取不到,返回false,获取到了返回true
tryLock(long timeout, TimeUnit unit) 在指定的时间内尝试获取这把锁,获取到了返回true,超过了指定的时间还没有获取到,则返回false
newCondition() 生成一个等待集合Condition对象

3 条件变量 Condition详解

Lock可以通过newCondition()方法 生成多个等待集合Condition对象。 Lock和Condition 是一对多的关系.。而synchronized 中也有条件变量,但是它只有一个waitSet等待集合对象。
就拿上厕所来举例说明:
现在有一家厕所,只有一个门(lock锁),里面有男厕所与女厕所。要想去上厕所,首先得获取这个门的使用权。假设有一个男的和一个女的都想上厕所,但是这个女的强不过这个男的,这个男的就获取了这个门的使用权(调用了lock.lock()获取了锁),但是要等待别人送厕纸过来(调用了男厕等待室Condition对象的await方法,进入了男厕等待室Condition对象的条件队列中),所以这个男的就进入了等待上男厕所的等待室中,但是你既然目前上不了厕所了,那你不能占着茅坑不拉屎吧,所以他得把这个门的使用权让出去。此时这个女的就获取了锁(调用了lock.lock()获取了锁),但是这个女的也等待别人送厕纸过来(调用了女厕等待室Condition对象的await方法,进入了女厕等待室Condition对象的条件队列中),所以这个女的就进入了等待上女厕所的等待室中。此时如果是哪个等待室的厕纸先送过来(调用了哪个等待室signal()),那么哪个等待室第一个进来等待的人就有资格重新去获取了这个门的使用权,如果获取到了,就上厕所,如果没有获取到就到这个门后面排队去(进入了同步队列中),等待获取门的使用权。
所以条件变量 Condition的使用流程是
await方法:
1、 await使用之前必须先获取锁的(必须先调用lock()方法获取到锁);
2、 await执行过程中会将当前线程放入到自己的conditionObject(条件变量)中等待,也就是说进入到自己的conditionObject的条件队列中然后释放掉获取的锁;
3、 直到await的线程被唤醒(或打断、或超时),然后再重新来竞争这把锁;
唤醒:调用signal方法来唤醒它。
4、 竞争不到,进入锁的同步队列中等待竞争到了继续执行await后面的事情然后最终又调用unlock方法释放调这把锁;
可以看到调用await方法实际上肯能会经历两次锁的获取与释放,一次是调用lock与unlock方法,一次是await方法内部的异常释放锁与重新再获取锁的操作。
signal方法:
1、 signal使用之前同样必须先获取锁的(必须先调用lock()方法获取到锁);
2、 signal执行过程中会将自己的conditionObject的条件队列中在队列排在第一位的等待的线程从条件队列移除出去,放到锁的同步队列中,让这个线程有机会获取到锁;
3、 signal使用完之调用unlock方法释放锁刚才signal方法内部不是把条件队列中的线程移到同步队列中了吗,那么此时调用unlock方法释放锁,就是唤醒同步队列中在队列排在第一位的等待线程那么是不是就相当于唤醒了由于调用await方法而等待的线程;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description:
 * @Package PACKAGE_NAME
 * @Author Zhai Quanjiang
 * @Date 2022/6/6 22:15
 * @Version: V1.0
 * @Other:
 */
public class ReentrantLockTest {
   
     
    public static ReentrantLock reentrantLock = new ReentrantLock(false);
    public static Object object = new Object();
    public static void main(String[] args) {
   
     
        test9();
    }

    public static void test9(){
   
     
        //男厕等待室
        Condition mensToiletCondition = reentrantLock.newCondition();
        //女厕等待室
        Condition womensToiletCondition = reentrantLock.newCondition();

        //男的厕纸的厕纸有没有送过来
        boolean[] mensToiletPaper = new boolean[]{
   
     false};
        //女的厕纸的厕纸有没有送过来
        boolean[] womensToiletPaper = new boolean[]{
   
     false};

        new Thread("小明"){
   
     
            @Override
            public void run() {
   
     
                reentrantLock.lock();

                while (!mensToiletPaper[0]){
   
     
                    System.out.println("小明的厕纸的厕纸没有送过来,小明进入了男厕等待室等待");
                    try {
   
     
                        mensToiletCondition.await();
                    } catch (InterruptedException e) {
   
     
                    }
                }
                System.out.println("小明的厕纸来了,可以上厕所了");
                reentrantLock.unlock();
            }
        }.start();
        new Thread("小红"){
   
     
            @Override
            public void run() {
   
     
                reentrantLock.lock();

                while (!womensToiletPaper[0]){
   
     
                    System.out.println("小红的厕纸的厕纸没有送过来,小红进入了女厕等待室等待");
                    try {
   
     
                        womensToiletCondition.await();
                    } catch (InterruptedException e) {
   
     
                    }
                }
                System.out.println("小红的厕纸来了,可以上厕所了");
                reentrantLock.unlock();
            }
        }.start();

        try {
   
     
            Thread.sleep(1000);//确保前面的两个线程已经启动起来了
        } catch (InterruptedException e) {
   
     
            e.printStackTrace();
        }
        new Thread("给小明送厕纸的人"){
   
     
            @Override
            public void run() {
   
     
                reentrantLock.lock();
                System.out.println("小明,你的厕纸来了");
                mensToiletPaper[0] = true;
                mensToiletCondition.signal();
                reentrantLock.unlock();
            }
        }.start();
        new Thread("给小红送厕纸的人"){
   
     
            @Override
            public void run() {
   
     
                reentrantLock.lock();
                System.out.println("小红,你的厕纸来了");
                womensToiletPaper[0] = true;
                womensToiletCondition.signal();
                reentrantLock.unlock();
            }
        }.start();
    }
}

执行结果
小明的厕纸的厕纸没有送过来,小明进入了男厕等待室等待
小红的厕纸的厕纸没有送过来,小红进入了女厕等待室等待
小明,你的厕纸来了
小明的厕纸来了,可以上厕所了
小红,你的厕纸来了
小红的厕纸来了,可以上厕所了

4 通过代码举例来说明以上特性

1.基本用法

public class ReentrantLockTest {
   
     
    public static void main(String[] args) {
   
     
        test1();
    }
    public static void test1(){
   
     
        ReentrantLock reentrantLock = new ReentrantLock(false);
        for (int i = 0; i <3; i++) {
   
     
            Thread thread = new Thread("thread"+i) {
   
     
                @Override
                public void run() {
   
     
                    try {
   
     
                        System.out.println(Thread.currentThread().getName() + "---准备获取锁");
                        reentrantLock.lock();
                        System.out.println(Thread.currentThread().getName() + "---获取了锁");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
   
     
                        e.printStackTrace();
                    } finally {
   
     
                        System.out.println(Thread.currentThread().getName() + "---释放了锁");
                        reentrantLock.unlock();
                    }
                }
            };
            thread.start();
        }
    }
}

运行结果
thread0—准备获取锁
thread2—准备获取锁
thread1—准备获取锁
thread0—获取了锁
thread0—释放了锁
thread2—获取了锁
thread2—释放了锁
thread1—获取了锁
thread1—释放了锁

2可重入

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

public class ReentrantLockTest {
   
     
    public static ReentrantLock reentrantLock = new ReentrantLock(false);
    public static void main(String[] args) {
   
     
        test2();
    }
     //----------------------可重入-------------
    public static void test2(){
   
     
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName() + "---获取了锁");
        try {
   
     
            System.out.println("干自己的事情1");
            test3();//test3方法也需要获取这个锁,如果ReentrantLock不是可重入锁的话,
                    //那么test2首先获取了锁,它要等到test3方法执行完才释放锁,而test3方法要等test2方法释放锁,才能获取锁,执行完自己。所以就发生了死锁
                    //从此处可以看出,可重入锁的特性预防了死锁的发生
            Thread.sleep(3000);
        } catch (InterruptedException e) {
   
     

        }
        System.out.println(Thread.currentThread().getName() + "---释放了锁");
        reentrantLock.unlock();
    }
    public static void test3(){
   
     
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName() + "---获取了锁");
        try {
   
     
            System.out.println("干自己的事情2");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
   
     

        }
        System.out.println(Thread.currentThread().getName() + "---释放了锁");
        reentrantLock.unlock();
    }

执行结果
main—获取了锁
干自己的事情1
main—获取了锁
干自己的事情2
main—释放了锁
main—释放了锁

synchronized的可重入

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description:
 * @Package PACKAGE_NAME
 * @Author Zhai Quanjiang
 * @Date 2022/6/6 22:15
 * @Version: V1.0
 * @Other:
 */
public class ReentrantLockTest {
   
     
    public static Object object = new Object();
    public static void main(String[] args) {
   
     
        test4();
    }

    public static void test4()  {
   
     
        synchronized (object){
   
     
            System.out.println(Thread.currentThread().getName() + "---获取了锁");
            System.out.println("干自己的事情3");
            try {
   
     
                Thread.sleep(3000);
                test5();
            } catch (InterruptedException e) {
   
     

            }
            System.out.println(Thread.currentThread().getName() + "---释放了锁");
        }
    }
    public static void test5()  {
   
     
        synchronized (object){
   
     
            System.out.println(Thread.currentThread().getName() + "---获取了锁");
            System.out.println("干自己的事情4");
            try {
   
     
                Thread.sleep(3000);
            } catch (InterruptedException e) {
   
     

            }
            System.out.println(Thread.currentThread().getName() + "---释放了锁");
        }
    }
}

执行结果
main—获取了锁
干自己的事情3
main—获取了锁
干自己的事情4
main—释放了锁
main—释放了锁

3.响应中断

lockInterruptibly()的方法响应中断:
如果线程在阻塞的过程中,被其他的线程给中断了,则此线程会被唤醒并且抛出一个中断异常

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description:
 * @Package PACKAGE_NAME
 * @Author Zhai Quanjiang
 * @Date 2022/6/6 22:15
 * @Version: V1.0
 * @Other:
 */
public class ReentrantLockTest {
   
     
    public static ReentrantLock reentrantLock = new ReentrantLock(false);
    public static Object object = new Object();
    public static void main(String[] args) {
   
     
        test6();
    }
    //----------------------可中断-------------
    public static void test6() {
   
     
        Thread thread = new Thread("thread1") {
   
     
            @Override
            public void run() {
   
     
                System.out.println(Thread.currentThread().getName() + "尝试获取锁");
                try {
   
     
                    reentrantLock.lockInterruptibly();
                } catch (InterruptedException e) {
   
     
                    System.out.println(Thread.currentThread().getName() + "被中断了");
                }
            }
        };
        //main线程获取锁
        System.out.println(Thread.currentThread().getName() + "尝试获取锁");
        reentrantLock.lock();
        thread.start();
        try {
   
     
            Thread.sleep(3000);
        } catch (InterruptedException e) {
   
     
        }
        //main线程中断thread1线程,此时thread1会被从阻塞队列中唤醒,并且抛出中断异常
        thread.interrupt();
    }
}

执行结果:
main尝试获取锁
thread1尝试获取锁
thread1被中断了
从执行结果看thread1被中断了,会被唤醒并且抛出一个中断异常

synchronized的不可响应中断

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description:
 * @Package PACKAGE_NAME
 * @Author Zhai Quanjiang
 * @Date 2022/6/6 22:15
 * @Version: V1.0
 * @Other:
 */
public class ReentrantLockTest {
   
     
    public static ReentrantLock reentrantLock = new ReentrantLock(false);
    public static Object object = new Object();
    public static void main(String[] args) {
   
     
        test7();
    }
    public static void test7(){
   
     
        Thread thread = new Thread("thread1") {
   
     
            @Override
            public void run() {
   
     
                System.out.println(Thread.currentThread().getName() + "尝试获取锁");
                try {
   
     
                    Thread.sleep(3000);//让main线程先获取锁
                } catch (InterruptedException e) {
   
     

                }
                synchronized (object){
   
     
                        //
                        System.out.println(Thread.currentThread().getName() + "干自己的事情");
                    }
            }
        };
        thread.start();
        //main线程获取锁
        synchronized (object){
   
     
            System.out.println(Thread.currentThread().getName() + "获取了锁");
            System.out.println(Thread.currentThread().getName() + "干自己的事情");
            //main线程中断thread1线程
            thread.interrupt();
            while (true){
   
     
                //死循环,不让main线程释放锁
            }
        }
    }
}

执行结果:
*
从执行结果可以看出thread1线程虽然被中断了,但是它依然还是在阻塞中

reentrantLock.lock()的不可响应中断

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description:
 * @Package PACKAGE_NAME
 * @Author Zhai Quanjiang
 * @Date 2022/6/6 22:15
 * @Version: V1.0
 * @Other:
 */
public class ReentrantLockTest {
   
     
    public static ReentrantLock reentrantLock = new ReentrantLock(false);
    public static Object object = new Object();
    public static void main(String[] args) {
   
     
        test8();
    }
    public static void test8(){
   
     
        Thread thread = new Thread("thread1") {
   
     
            @Override
            public void run() {
   
     
                System.out.println(Thread.currentThread().getName() + "尝试获取锁");
                reentrantLock.lock();
                System.out.println(Thread.currentThread().getName() + "获取了锁");
                System.out.println(Thread.currentThread().getName() + "干自己的事情");
                reentrantLock.unlock();
            }
        };
        //main线程先获取了锁
        System.out.println(Thread.currentThread().getName() + "尝试获取锁");
        System.out.println(Thread.currentThread().getName() + "获取了锁");
        reentrantLock.lock();
        thread.start();
        //main线程中断了线程thread1
        thread.interrupt();
    }
}

执行结果:
*
从执行结果可以看出thread1线程虽然被中断了,但是它依然还是在阻塞中