欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

并发编程(三)—— ReentrantLock实现原理及源码分析

程序员文章站 2022-07-01 08:25:29
ReentrantLock是Java并发包中提供的一个可重入的互斥锁。ReentrantLock和synchronized在基本用法,行为语义上都是类似的,同样都具有可重入性。只不过相比原生的Synchronized,ReentrantLock增加了一些高级的扩展功能,比如它可以实现公平锁,同时也可 ......

  reentrantlock是java并发包中提供的一个可重入的互斥锁reentrantlocksynchronized在基本用法,行为语义上都是类似的,同样都具有可重入性。只不过相比原生的synchronized,reentrantlock增加了一些高级的扩展功能,比如它可以实现公平锁,同时也可以绑定多个conditon

可重入性/公平锁/非公平锁

可重入性

      所谓的可重入性,就是可以支持一个线程对锁的重复获取,原生的synchronized就具有可重入性,一个用synchronized修饰的递归方法,当线程在执行期间,它是可以反复获取到锁的,而不会出现自己把自己锁死的情况。reentrantlock也是如此,在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。

公平锁/非公平锁

  所谓公平锁,顾名思义,意指锁的获取策略相对公平,当多个线程在获取同一个锁时,必须按照锁的申请时间来依次获得锁,排排队,不能插队;非公平锁则不同,当锁被释放时,等待中的线程均有机会获得锁。synchronized是非公平锁,reentrantlock默认也是非公平的,但是可以通过带boolean参数的构造方法指定使用公平锁,但非公平锁的性能一般要优于公平锁。

  synchronized是java原生的互斥同步锁,使用方便,对于synchronized修饰的方法或同步块,无需再显式释放锁。而reentrantlock做为api层面的互斥锁,需要显式地去加锁解锁。采用lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。

class x {
    private final reentrantlock lock = new reentrantlock();
    // ...
 
    public void m() {
      lock.lock();  // 加锁
      try {
        // ... 函数主题
      } finally {
        lock.unlock() //解锁
      }
    }
}

源码分析

  接下来我们从源码角度来看看reentrantlock的实现原理,它是如何保证可重入性,又是如何实现公平锁的。

1、无参构造器(默认为非公平锁)

public reentrantlock() {
     sync = new nonfairsync();//默认是非公平的
}

sync是reentrantlock内部实现的一个同步组件,它是reentrantlock的一个静态内部类,继承于aqs。

2、带布尔值的构造器(是否公平)

public reentrantlock(boolean fair) {
        sync = fair ? new fairsync() : new nonfairsync();//fair为true,公平锁;反之,非公平锁
}

此处可以指定是否采用公平锁,failsync和nonfailsync亦为reentrantlock的静态内部类,都继承于sync

3、lock()

public void lock() {
        sync.lock();//代理到sync的lock方法上
}

sync的lock方法是抽象的,实际的lock会代理到fairsync或是nonfairsync上(根据用户的选择来决定,公平锁还是非公平锁)

4、unlock()

public void unlock() {
        sync.release(1);//释放锁
}

释放锁,调用sync的release方法。

5、trylock()

lock lock = ...;
if(lock.trylock()) {
     try{
         //处理任务
     }catch(exception ex){
         
     }finally{
         lock.unlock();   //释放锁
     } 
}else {
    //如果不能获取锁,则直接做其他事情
}

trylock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false。

6、newcondition()

public condition newcondition() {
        return sync.newcondition();
}

获取一个conditon,reentrantlock支持多个condition

7、await()

public class myservice {

    private lock lock = new reentrantlock();
    private condition condition=lock.newcondition();
    public void testmethod() {
        
        try {
            lock.lock();
            system.out.println("开始wait");
            condition.await();
            for (int i = 0; i < 5; i++) {
                system.out.println("threadname=" + thread.currentthread().getname()
                        + (" " + (i + 1)));
            }
        } catch (interruptedexception e) {
            // todo 自动生成的 catch 块
            e.printstacktrace();
        }
        finally
        {
            lock.unlock();
        }
    }

}

通过创建condition对象来使线程wait,必须先执行lock.lock方法获得锁

8、signal()

public void signal() {
        try {
            lock.lock();
            condition.signal();
        } finally {
            lock.unlock();
        }
}

condition对象的signal方法可以唤醒wait线程

9、创建多个condition对象

  一个condition对象的signal(signalall)方法和该对象的await方法是一一对应的,也就是一个condition对象的signal(signalall)方法不能唤醒其他condition对象的await方法

 

abc循环打印20遍

  1 package main.java.juc;
  2 
  3 import java.util.concurrent.locks.condition;
  4 import java.util.concurrent.locks.lock;
  5 import java.util.concurrent.locks.reentrantlock;
  6 
  7 /*
  8  * 编写一个程序,开启 3 个线程,这三个线程的 id 分别为 a、b、c,每个线程将自己的 id 在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。
  9  *    如:abcabcabc…… 依次递归
 10  */
 11 public class testabcalternate {
 12     
 13     public static void main(string[] args) {
 14         alternatedemo ad = new alternatedemo();
 15         
 16         new thread(new runnable() {
 17             @override
 18             public void run() {
 19                 for (int i = 1; i <= 20; i++) {
 20                     ad.loopa(i);
 21                 }
 22             }
 23         }, "a").start();
 24         
 25         new thread(new runnable() {
 26             @override
 27             public void run() {
 28                 for (int i = 1; i <= 20; i++) {
 29                     ad.loopb(i);
 30                 }
 31             }
 32         }, "b").start();
 33         
 34         new thread(new runnable() {
 35             @override
 36             public void run() {
 37                 for (int i = 1; i <= 20; i++) {
 38                     ad.loopc(i);
 39                     system.out.println("-----------------------------------");
 40                 }
 41             }
 42         }, "c").start();
 43     }
 44 
 45 }
 46 
 47 class alternatedemo{
 48     
 49     private int number = 1; //当前正在执行线程的标记
 50     
 51     private lock lock = new reentrantlock();
 52     private condition condition1 = lock.newcondition();
 53     private condition condition2 = lock.newcondition();
 54     private condition condition3 = lock.newcondition();
 55     
 56     /**
 57      * @param totalloop : 循环第几轮
 58      */
 59     public void loopa(int totalloop){
 60         lock.lock();
 61         try {
 62             //1. 判断
 63             if(number != 1){
 64                 condition1.await();
 65             }
 66             //2. 打印
 67             for (int i = 1; i <= 1; i++) {
 68                 system.out.println(thread.currentthread().getname() + "\t" + i + "\t" + totalloop);
 69             }
 70             //3. 唤醒
 71             number = 2;
 72             condition2.signal();
 73         } catch (exception e) {
 74             e.printstacktrace();
 75         } finally {
 76             lock.unlock();
 77         }
 78     }
 79     
 80     public void loopb(int totalloop){
 81         lock.lock();
 82         try {
 83             //1. 判断
 84             if(number != 2){
 85                 condition2.await();
 86             }
 87             //2. 打印
 88             for (int i = 1; i <= 1; i++) {
 89                 system.out.println(thread.currentthread().getname() + "\t" + i + "\t" + totalloop);
 90             }
 91             //3. 唤醒
 92             number = 3;
 93             condition3.signal();
 94         } catch (exception e) {
 95             e.printstacktrace();
 96         } finally {
 97             lock.unlock();
 98         }
 99     }
100     
101     public void loopc(int totalloop){
102         lock.lock();
103         try {
104             //1. 判断
105             if(number != 3){
106                 condition3.await();
107             }
108             //2. 打印
109             for (int i = 1; i <= 1; i++) {
110                 system.out.println(thread.currentthread().getname() + "\t" + i + "\t" + totalloop);
111             }
112             //3. 唤醒
113             number = 1;
114             condition1.signal();
115         } catch (exception e) {
116             e.printstacktrace();
117         } finally {
118             lock.unlock();
119         }
120     }
121     
122 }

运行结果:

并发编程(三)—— ReentrantLock实现原理及源码分析

代码分析:

  三个线程分别循环20次调用loopa、loopb、loopc打印,但是不确定是哪个方法先被调用到,如果是loopb先调用,则loopb方法先获取到锁,loopa和loopc等待锁,此时线程执行标记number=1,代码84行处为true,则condition2.await();如果需要唤醒此线程,则需要用condition2来唤醒,此时线程交出锁;

  如果loopa获取了锁,loopb和loopc等待锁,此时线程执行标记number=1,代码63行处为false,则执行67行打印,打印完则用condition2.signal()唤醒打印loopb的线程,接着loopb的线程去打印b,线程loopb打印完毕去唤醒打印loopc的线程,打印完loopc再唤醒loopa,如此循环20次。

 总结

 1、lock是一个接口,而synchronized是java中的关键字,synchronized是内置的语言实现;

 2、synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用lock时需要在finally块中释放锁;

 3、lock类可以创建condition对象,condition对象用来是线程等待和唤醒线程,需要注意的是condition对象的唤醒的是用同一个condition执行await方法的线程,所以也就可以实现唤醒指定类的线程

上一篇: C++的特点

下一篇: Spring