ReentrantLock详解

目录

一、ReentrantLock的含义

二、RerntrantLock当中的常用方法

     ①lock()和unlock()方法

   ②构造方法

   ③tryLock()方法

       tryLock()无参数

        tryLock(timeout,Times)有参数

    ④lockInterruptibly() throws InterruotedException

经典面试问题: 

ReentrantLock和synchronized有什么不同


一、ReentrantLock的含义

        ReentrantLock也是Java当中提供的一种锁。这种锁和synchronized类似也可以起到互斥使用,保证线程安全的的作用。

        关于synchronized的作用,已经在这一篇文章当中提及:

        (3条消息) Java对于synchronized的初步认识_革凡成圣211的博客-CSDN博客https://blog.csdn.net/weixin_56738054/article/details/128062475?spm=1001.2014.3001.5501

        但是,仍然在使用的语法上面和synchronized有一些差别。下面,将具体介绍一下ReentrantLock的使用以及ReentrantLock的各个特性。


二、RerntrantLock当中的常用方法

     ①lock()和unlock()方法

        顾名思义,lock()方法就是线程进入同步代码块之后的加锁操作,而unlock()就是需要离开代码块之后的解锁操作。ReentrantLock详解_第1张图片

       在上述代码当中,锁就是lock对象。当多个线程同时尝试调用lock.lock()方法之后,只有其中一个线程可以获得锁,其余线程都需要阻塞等待。当线程执行到unlock()方法之后,说明已经解锁了,其他线程可以继续获取lock。


       但是, 上面的写法不是规范的写方法,规范的写法,应当把lock.unlock()写进finally代码块当中,并且lock.lock()方法需要在try方法上方的第一行

’ 如下图所示:

ReentrantLock详解_第2张图片

       原因:

       如果在上述代码当中,出现了if语句,线程进入if语句之后调用了lock.lock()方法加锁成功。但是线程离开if语句的时候,没有调用lock.unlock()方法,这样也就意味着线程提前返回了,没有解锁。那么其他线程如果有阻塞等待的,将一直阻塞等待。   

       因此,为了防止忘记解锁的情况,应当把lock.unlock()放到finally当中。

       如图:故意不解锁,看看有什么后果。场景,此时有两个线程,一个是thread1,另外一个是thread2。两个线程分别通过同一个对象count调用add()方法:

ReentrantLock详解_第3张图片      运行:

     ReentrantLock详解_第4张图片

可以看到,控制台始终输出了-->"现在的线程是....thread1...",说明thread2无法再次获取到锁。


   ②构造方法

ReentrantLock lock1=new ReentrantLock(true);
        
ReentrantLock lock=new ReentrantLock(false);

       如果构造方法当中,指定了true作为参数,那么lock将是公平锁。如果没有指定布尔值,或者指定了布尔值为false,那么lock将是非公平锁。


   ③tryLock()方法

       tryLock()无参数

        tryLock()方法有两个作用:

       当调用lock.tryLock()的时候,如果lock此时还没有被其他线程占有,那么它会立刻获取到锁,并且返回true。

        如果lock已经被其他线程占用了,那么调用lock.tryLock()的线程将不会阻塞等待,而是继续往下执行。       


        tryLock(timeout,Times)有参数

         当线程调用lock.tryLock(timeout,TimeUtil.时间单位常量)

         方法的时候,会发生以下的情况:

         (1)当前线程将会在lock.tryLock(timeout,TimeUtil.时间单位常量)这行代码处阻塞等待timeout时间,如果获取到锁的线程在这个timeout时间内释放锁了,那么正在等待的线程可以重新获取锁。

         (2)如果阻塞等待的线程直到timeout时间了,加锁的线程仍然没有释放锁,那么原来在等待的线程将不再等待,直接返回。

        (3)如果超时等待的线程在等待锁释放的timeout时间内被中断(其他线程调用t.interrupt())方法中断正在等待的线程,那么当前正在等待的线程会抛出InterruptException,也会终止等待

        因此,正确使用tryLock()的方式为:首先进行判断,如果得到结果为false,也就是获取不到锁,直接return返回即可。

        ReentrantLock详解_第5张图片


    ④lockInterruptibly() throws InterruotedException

      这个方法,类似于"lock"也是属于"加锁"的方法。 

      和单纯的lock()方法不同,线程调用了lockInterrupt()方法之后,可以"响应中断"式地加锁。

      假设,在某一时刻,t1线程获取到锁,在t1调用lockInterruptibly()方法获取到锁之后,如果t2也调用这个方法获取锁,那么t2会进入阻塞等待的状态。

      如果t2在阻塞等待的过程当中,被其他线程调用t2.interrupt()方法,那么线程t2会被触发异常(InterruptException),并且被"唤醒"。


     代码实现:

     add()方法,使用sleep(1000)的目的是减慢循环的速度

class Count1{
    public int number;
    ReentrantLock lock=new ReentrantLock();
    public void add(){
        try {
            //使用"可中断"式地加锁
            lock.lockInterruptibly();
            //标志位默认为false
            while (true){
                number++;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

      启动t1,t2线程,让t1先获取到锁,t2后面才获取到锁:

public class ThreadDemo32 {
    public static void main(String[] args) {
        Count1 count1=new Count1();
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                count1.add();
            }
        },"t1");
        t1.start();
        //让主线程休眠1000毫秒,确保t1一定启动成功了
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                count1.add();
            }
        },"t2");
        t2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断t2
        t2.interrupt();
    }
}

      图解:

      ReentrantLock详解_第6张图片

但是,一运行程序,发现出现了下面的问题:

  ReentrantLock详解_第7张图片

出现第一个问题的原因我们找到了,那出现第二个问题,,锁状态异常又是什么原因呢? 

回到add()方法当中:

ReentrantLock详解_第8张图片

 这就是为什么thread2在被唤醒之后,触发锁状态异常的原因。


经典面试问题: 

ReentrantLock和synchronized有什么不同

        ①ReentrantLock可以提供公平+非公平两种特性,当构造方法中指定了参数为true的时候,这个锁被确定为公平锁。

      而synchronized无法提供公平锁的特性


        ②ReentrantLock的加锁、解锁操作都是需要手动进行,而synchronized的话可以进行自动的加锁、解锁操作。

       synchronized可以有效避免加锁之后忘记解锁的情况。


        ③synchronized无法提供lock.tryLock()这样的特性,而ReentrantLock可以提供。线程如果在指定的时间之内无法获取到锁,或者锁已经被占用了,那么lock.tryLock()可以有效减少线程阻塞等待的情况,或者减少阻塞等待的时间,

       而synchronized只会让无法获取到锁的线程"死等"。直到获取到锁的线程释放锁


        ④ReentrantLock在调用lock.lockInterruptibly()时候,可以让阻塞等待的线程被提前"唤醒",但是synchronized不可以。具体的操作已经在上面解释了。


       ⑤ReentrantLock是Java当中的一个具体的,是在API级别提供的锁,

       而synchronized是Java当中提供的一个关键字,是JVM级别提供的锁


      ⑥当代码执行到synchronized修饰的代码块的时候,如果在同步代码块内部发生了异常,没有及时处理的话,会提前退出并且让线程释放锁。

      而ReentrantLock无法做到立刻解锁,因此,unLock()的解锁操作一定要在finally代码块当中,避免加锁之后忘记解锁的情况。 

你可能感兴趣的:(java,开发语言)