重入锁----ReentrantLock

本节主要从下述四个方面介绍重入锁。

1.什么是重入锁?

2.为什么要引用重入锁?

3.重入锁是怎么实现的?

4.分析java并发包中ReentrantLock。

什么是重入锁

重入锁,支持重进入的锁,表示该锁能够支持一个线程对它重复加锁,即线程在获得锁之后再次获取该锁时不会被阻塞。

为什么要引用重入锁?

以子类重写父类方法为例:

Mutix是不支持重入的锁。(代码摘抄自《java并发编程的艺术》)

 1 import java.util.concurrent.TimeUnit;
 2 import java.util.concurrent.locks.*;
 3
 4
 5
 6 public class Mutix implements Lock
 7 {
 8     private static class Sync extends AbstractQueuedSynchronizer{
 9         //是否处于占用状态
10         protected boolean isHeldExclusively(){
11             return this.getState()==1;
12         }
13         protected boolean tryAcquire(int acquires){
14             if(compareAndSetState(0,1)){
15                 setExclusiveOwnerThread(Thread.currentThread());
16                 return true;
17             }
18             return false;
19
20         }
21         protected boolean tryRelease(int releases){
22             if(getState()==0)throw new IllegalMonitorStateException();
23             setExclusiveOwnerThread(null);
24             setState(0);
25
26             return true;
27
28         }
29         Condition newCondition(){
30             return new ConditionObject();
31         }
32
33     }
34     private final Sync sync=new Sync();
35
36     @Override
37     public void lock()
38     {
39         // TODO Auto-generated method stub
40     sync.acquire(1);
41     }
42
43     @Override
44     public void lockInterruptibly() throws InterruptedException
45     {
46         // TODO Auto-generated method stub
47 sync.acquireInterruptibly(1);
48     }
49
50     @Override
51     public Condition newCondition()
52     {
53         // TODO Auto-generated method stub
54         return sync.newCondition();
55     }
56
57     @Override
58     public boolean tryLock()
59     {
60         // TODO Auto-generated method stub
61         return sync.tryAcquire(1);
62     }
63
64     @Override
65     public boolean tryLock(long arg0, TimeUnit arg1)
66             throws InterruptedException
67     {
68         // TODO Auto-generated method stub
69         return sync.tryAcquireNanos(1, arg1.toNanos(arg0));
70     }
71
72     @Override
73     public void unlock()
74     {
75         // TODO Auto-generated method stub
76 sync.release(1);
77     }
78     public boolean hasQueuedThreads(){
79         return sync.hasQueuedThreads();
80     }
81
82 }

分别以支持重入和不支持重入进行测试;

测试代码如下:

 1 import java.util.concurrent.locks.ReentrantLock;
 2
 3
 4 public class TestLock
 5 {
 6     static Mutix lock=new Mutix();
 7 //    static ReentrantLock lock=new ReentrantLock();
 8
 9     public static class Widget {
10         public  void doSomething() {
11             lock.lock();
12             try{
13             System.out.println("父类Widget,线程名称:" +Thread.currentThread().getName());
14             }finally{
15                 lock.unlock();
16             }
17         }
18     }
19
20     public static class LoggingWidget extends Widget {
21         public  void doSomething() {
22             lock.lock();
23             try{
24                  System.out.println("子类LoggingWidget: calling doSomething,线程名称:" +Thread.currentThread().getName());
25             super.doSomething();
26             }finally{
27                 lock.unlock();
28             }
29         }
30     }
31
32
33
34     /**
35      * @param args
36      */
37     public static void main(String[] args)
38     {
39         LoggingWidget test=new LoggingWidget();
40         test.doSomething();
41     }
42
43 }

当使用ReentrantLock可重入锁时,显示结果如下:

子类LoggingWidget: calling doSomething,线程名称:main
父类Widget,线程名称:main

当使用Mutix不可重入锁时,结果如下:

子类LoggingWidget: calling doSomething,线程名称:main

此时程序并没有结束,而是一直在等待父类的锁,通过jstack命令,查看到Main进程处于等待状态。按正常的处理逻辑,子类是允许调用父类的方法,故重入锁在实际应用中还是很重要的。

重入锁是怎么实现?

1)线程再次获取锁。需要判断当前获取锁的线程是否为当前占据锁的线程,如果是,则再次获取成功。

2)锁的最终释放。若线程重复了N次获得了锁,那需要释放N次才能真正释放该锁。线程再次获取锁时,计时器加1,当释放锁时,计数器减1。

分析java并发包中ReentrantLock。

1)自定义组合同步器实现锁的获取和释放,默认是以非公平形式获取锁。

 1         final boolean nonfairTryAcquire(int acquires) {
 2             final Thread current = Thread.currentThread();//当前线程
 3             int c = getState();//获取同步状态
 4             if (c == 0) {//如果同步状态为0,表示当前没有线程获取锁
 5                 if (compareAndSetState(0, acquires)) {//通过CAS算法获取锁
 6                     setExclusiveOwnerThread(current);
 7                     return true;
 8                 }
 9             }
10             else if (current == getExclusiveOwnerThread()) {
11                 //如果同步状态不为0,表示已经有线程获取了该锁,判断获取锁的线程是否为当前线程,如果时,同步状态加acquires,一般为加1
12                 int nextc = c + acquires;
13                 if (nextc < 0) // overflow
14                     throw new Error("Maximum lock count exceeded");
15                 setState(nextc);//设置同步状态
16                 return true;
17             }
18             return false;
19         }
20
21         protected final boolean tryRelease(int releases) {
22             int c = getState() - releases;//计算当前同步状态减去releases
23             if (Thread.currentThread() != getExclusiveOwnerThread())//如果当前线程不等于获取锁的线程
24                 throw new IllegalMonitorStateException();
25             boolean free = false;
26             if (c == 0) {//如果C==0,表示同步状态完全释放,将占用线程设置为Null,并且返回true
27                 free = true;
28                 setExclusiveOwnerThread(null);
29             }
30             setState(c);
31             return free;
32         }

在调用ReentrantLock的lock和unlock方法时,实际是调用同步器中的方法。

 1  public boolean tryLock() {
 2         return sync.nonfairTryAcquire(1);
 3     }
 4
 5 public boolean tryLock(long timeout, TimeUnit unit)
 6             throws InterruptedException {
 7         return sync.tryAcquireNanos(1, unit.toNanos(timeout));
 8     }
 9  public void unlock() {
10         sync.release(1);
11     }

从代码中可以看到,最终调用的是sync中的方法,对于用户而言,sync是透明的。

2)公平和非公平获取锁。

 1  /**
 2      * Sync object for non-fair locks
 3      */
 4     static final class NonfairSync extends Sync {
 5         private static final long serialVersionUID = 7316153563782823691L;
 6
 7         /**
 8          * Performs lock.  Try immediate barge, backing up to normal
 9          * acquire on failure.
10          */
11         final void lock() {
12             if (compareAndSetState(0, 1))
13                 setExclusiveOwnerThread(Thread.currentThread());
14             else
15                 acquire(1);
16         }
17
18         protected final boolean tryAcquire(int acquires) {
19             return nonfairTryAcquire(acquires);
20             //直接调用Sync的方法。
21         }
22     }

 1  static final class FairSync extends Sync {
 2         private static final long serialVersionUID = -3000897897090466540L;
 3
 4         final void lock() {
 5             acquire(1);
 6         }
 7
 8         /**
 9          * Fair version of tryAcquire.  Don‘t grant access unless
10          * recursive call or no waiters or is first.
11          */
12         protected final boolean tryAcquire(int acquires) {
13             final Thread current = Thread.currentThread();
14             int c = getState();
15             if (c == 0) {
16                 if (!hasQueuedPredecessors() &&
17                     compareAndSetState(0, acquires)) {
18                     setExclusiveOwnerThread(current);
19                     return true;
20                 }
21             }
22             else if (current == getExclusiveOwnerThread()) {
23                 int nextc = c + acquires;
24                 if (nextc < 0)
25                     throw new Error("Maximum lock count exceeded");
26                 setState(nextc);
27                 return true;
28             }
29             return false;
30         }
31     }

公平和非公平最大区别在于:公平锁需要判断当前节点的前一个节点是否为头节点(hasQueuedPredecessors() ),如果是,才允许通过CAS算法获取锁,如果不是,表示有线程比当前线程更早的获取锁,因此需要等待前驱线程获取锁并释放锁之后才能继续获取锁。非公平不需要进行判断。公平锁保证了线程处理之间的公平,通过FIFO,保证了公平性,而代价是进行大量的线程切换。非公平锁虽然有时会造成线程饥饿,但极少的线程切换,保证了更大的吞吐量。

ReentrantLock锁默认采用的是非公平锁。

1  public ReentrantLock() {
2         sync = new NonfairSync();//默认采用非公平锁
3     }

总结:最近这几天在看《JAVA并发编程的艺术》,作者讲解的很详细,本来不想写博客,网上关于这些的文章已经非常多了,而且该书讲解的那么透彻,但后来想想,在写博客其实也是整理、梳理知识的过程,如果自己都没有弄明白,如何能够写出来。

时间: 2025-01-31 00:36:32

重入锁----ReentrantLock的相关文章

Java并发程序设计(12)并发锁之可重入锁ReentrantLock

1.1. 可重入锁ReentrantLock ReentrantLock是java并发库中提供的可重入锁.与synchronized同步块相比,有相似也有不同.相似的地方有: (1)都可以实现多线程之间的同步,避免对共享资源的访问冲突. (2)都是可重入的,即一个已经获取锁的线程可以再次获得同一个锁,synchronized也类似. 不同的地方有: (1)ReentrantLock更灵活,获取锁和释放锁可以在同一个方法中,也可以在不同方法中.synchronized通常用在同一个方法体内. (2

java多线程---重入锁ReentrantLock

1.定义 重入锁ReentrantLock,支持重入的锁,表示一个线程对资源的重复加锁. 2.底层实现 每个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法:成功后,JVM会记下锁的持有线程,并且将计数器置为1:此时其它线程请求该锁,则必须等待:而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增:当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁. 3.使用样例 eg: import java

java 可重入锁ReentrantLock的介绍

一个小例子帮助理解 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户户有水用的目标,村长绞尽脑汁,最终想出了一个比较合理的方案. 首先,在水井边上安排一个看井人,负责维持秩序. 然后,打水时,以家庭为单位,哪个家庭任何人先到井边,就可以先打水,而且如果一个家庭占到了打水权,其家人这时候过来打水不用排队.而那些没有抢占到打水权的人,一个一个挨着在井边排成一队,先到的排在前面. 最后,打水的人打完水以后就告诉看井人,看

JAVA多线程重入锁ReentrantLock应用

package concurrent; import java.util.concurrent.*; import java.util.concurrent.locks.ReentrantLock; /** * @Auther:zhl * @Date:2019/7/13 * @Description: 并发测试,重入锁ReentrantLock解决并发问题 */ public class ConcurrentSample { //并发线程数量 private static int users =

经典笔试题:线程通信(使用重入锁(ReentrantLock)和条件队列(Condition)实现线程间通信)

经典笔试题: 1.自定义容器,提供新增元素(add)和获取元素数量(size)方法.2.启动两个线程.线程1向容器中新增10个数据.线程2监听容器元素数量,当容器元素数量为5时,线程2输出信息并终止. package com.gaopeng.programming.test2; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.conc

Java 重入锁 ReentrantLock 原理分析

1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生.ReentrantLock 的主要功能和 synchronized 关键字一致,均是用于多线程的同步.但除此之外,ReentrantLock 在功能上比 synchronized 更为丰富.比如 ReentrantLock 在加锁期间,可响应中断,可设置超时等. ReentrantLock 是我们

重入锁 ReentrantLock (转)(学习记录)

重入锁(ReentrantLock)是一种递归无阻塞的同步机制.以前一直认为它是synchronized的简单替代,而且实现机制也不相差太远.不过最近实践过程中发现它们之间还是有着天壤之别. 以下是官方说明:一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大.ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有.当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取

Java并发(九):重入锁 ReentrantLock

一.ReentrantLock类结构 public class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; // 锁 大部分功能都是委托给Sync来实现的 abstract static class Sync extends AbstractQueuedSynchronizer {} static final class FairSync extends Sync {} static

Java 重入锁 ReentrantLock

本篇博客是转过来的. 但是略有改动感谢 http://my.oschina.net/noahxiao/blog/101558 摘要 从使用场景的角度出发来介绍对ReentrantLock的使用,相对来说容易理解一些. 场景1:如果发现该操作已经在执行中则不再执行(有状态执行) a.用在定时任务时,如果任务执行时间可能超过下次计划执行时间,确保该有状态任务只有一个正在执行,忽略重复触发. b.用在界面交互时点击执行较长时间请求操作时,防止多次点击导致后台重复执行(忽略重复触发). 以上两种情况多用