博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
并发编程(三)—— ReentrantLock的用法
阅读量:5905 次
发布时间:2019-06-19

本文共 7303 字,大约阅读时间需要 24 分钟。

  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 }

运行结果:

代码分析:

  三个线程分别循环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方法的线程,所以也就可以实现唤醒指定类的线程

转载于:https://www.cnblogs.com/java-chen-hao/p/10037209.html

你可能感兴趣的文章
使用qq邮箱的smpt服务发送邮件一定要记得用ssl
查看>>
20个非常有用的Java代码片段
查看>>
转 ubuntu解压命令全览
查看>>
Android开发的前景分析——之你为何看好Android?
查看>>
linux学习笔记
查看>>
页面自动刷新
查看>>
No free lunch in search and optimization
查看>>
分析 Spring 的编程式事务管理及声明式事务管理(转)
查看>>
网站优化和竞价有什么区别
查看>>
MySQL开源热备工具XtraBackup的原理与程序说明
查看>>
mongoDB(1):windows下安装mongoDB(解压缩版)
查看>>
CentOS修改主机名
查看>>
php 5.3.6中php-fpm 配置
查看>>
XMPP协议分析-原理篇
查看>>
centos7常用操作
查看>>
Sysprep工具封装系统
查看>>
系统集成资质培训 - 新书发布
查看>>
Ubuntu解决RTNETLINK answers: File exists
查看>>
ES6数组去重的最佳实践:Set结合Array.from() | 拓展运算符结合 Set
查看>>
深入屏幕像素概念
查看>>