- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Java并发编程之ReentrantLock实现原理及源码剖析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
前面《Java并发编程之JUC并发核心AQS同步队列原理剖析》介绍了AQS的同步等待队列的实现原理及源码分析,这节我们将介绍一下基于AQS实现的ReentranLock的应用、特性、实现原理及源码分析.
ReentrantLock位于Java的juc包里面,从JDK1.5开始出现,是基于AQS同步队列的独占模式实现的一种锁。ReentrantLock使用起来比synchronized更加灵活,可以自己控制加锁、解锁的逻辑。ReentrantLock跟synchronized一样也是可重入的锁,提供了公平/非公平两种模式:
通过new ReentrantLock()的方式创建的是非公平锁,要想创建公平锁需要在构造方法中指定new ReentrantLock(true)。ReentrantLock的常用方法如下:
ReentrantLock的使用方式一般如下,一定要在finally里面进行解锁,防止程序出现异常无法解锁 。
1
2
3
4
5
6
7
8
9
|
ReentrantLock lock =
new
ReentrantLock();
lock.lock();
try
{
System.out.println(
"获取了锁"
);
}
catch
(Exception e) {
e.printStackTrace();
}
finally
{
lock.unlock();
}
|
下面通过一个程序示例,演示一下ReentrantLock的使用:对同一个lock对象做多次加锁,解锁,演示一下ReentrantLock的锁重入 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public
class
ReentrantLockTest {
private
Integer counter =
0
;
private
ReentrantLock lock =
new
ReentrantLock();
public
void
modifyResources(String threadName){
System.out.println(
"线程:--->"
+threadName+
"等待获取锁"
);
lock.lock();
System.out.println(
"线程:--->"
+threadName+
"第一次加锁"
);
counter++;
System.out.println(
"线程:"
+threadName+
"做第"
+counter+
"件事"
);
//重入该锁,我还有一件事情要做,没做完之前不能把锁资源让出去
lock.lock();
System.out.println(
"线程:--->"
+threadName+
"第二次加锁"
);
counter++;
System.out.println(
"线程:"
+threadName+
"做第"
+counter+
"件事"
);
lock.unlock();
System.out.println(
"线程:"
+threadName+
"释放一个锁"
);
lock.unlock();
System.out.println(
"线程:"
+threadName+
"释放一个锁"
);
}
public
static
void
main(String[] args)
throws
InterruptedException {
ReentrantLockTest tp =
new
ReentrantLockTest();
new
Thread(()->{
String threadName = Thread.currentThread().getName();
tp.modifyResources(threadName);
},
"Thread:张三"
).start();
new
Thread(()->{
String threadName = Thread.currentThread().getName();
tp.modifyResources(threadName);
},
"Thread:李四"
).start();
Thread.sleep(
100
);
}
}
|
程序运行输出如下所示:上面代码中lock加锁两次然后解锁两次,在张三线程两次解锁完成之前,李四线程一直在等待。ReentrantLock加锁了几次,就要解锁相同的次数才可以释放锁.
线程:--->Thread:张三等待获取锁 线程:--->Thread:张三第一次加锁 线程:Thread:张三做第1件事 线程:--->Thread:张三第二次加锁 线程:--->Thread:李四等待获取锁 线程:Thread:张三做第2件事 线程:Thread:张三释放一个锁 线程:Thread:张三释放一个锁 线程:--->Thread:李四第一次加锁 线程:Thread:李四做第3件事 线程:--->Thread:李四第二次加锁 线程:Thread:李四做第4件事 线程:Thread:李四释放一个锁 线程:Thread:李四释放一个锁 。
ReentrantLock实现了Lock接口,它有一个内部类Sync实现了前面介绍过的AbstractQueuedSynchronizer,而其公平锁、非公平锁分别通过Sync的子类FairSync、NonFairSync(也是ReentrantLock的内部类)实现。下面看下其UML图 。
lock()方法调用时序图如下:
前面《Java并发编程之JUC并发核心AQS同步队列原理剖析》介绍AQS的时候说过,AbstractQueuedSynchronizer中有一个状态变量state,在ReentrantLock中state等于0表示没有线程获取锁,如果等于1说明有线程获取了锁,如果大于1说明获取锁的线程加锁的次数,加了几次锁就必须解锁几次,每次unlock解锁state都会减1,减到0时释放锁.
前面一篇博客《Java并发编程之JUC并发核心AQS同步队列原理剖析》对AQS介绍的已经非常详细了,所以下面源码分析中牵涉AQS中的方法就不再进行介绍了,想了解的话可以看下那篇博客.
先看下非公平锁的加锁lock方法,lock方法中调用了sync的lock方法,而sync对象时根据构造ReentrantLock时是公平锁(FairSync)还是非公平锁(NonFairSync).
1
2
3
|
public
void
lock() {
sync.lock();
}
|
这里调用的是非公平锁,所以我们看下 NonFairSync的lock方法:进来时不管有没有其他线程持有锁或者等待锁,会先调用AQS中的compareAndSetState方法尝试获取锁,如果获取失败,会调用AQS中的acquire方法 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
final
void
lock() {
/**
* 第一步:直接尝试加锁
* 与公平锁实现的加锁行为一个最大的区别在于,此处不会去判断同步队列(CLH队列)中
* 是否有排队等待加锁的节点,上来直接加锁(判断state是否为0,CAS修改state为1)
* ,并将独占锁持有者 exclusiveOwnerThread 属性指向当前线程
* 如果当前有人占用锁,再尝试去加一次锁
*/
if
(compareAndSetState(
0
,
1
))
setExclusiveOwnerThread(Thread.currentThread());
else
//AQS定义的方法,加锁
acquire(
1
);
}
|
下面看下acquire方法,会先调用NonFairSync类中重写的tryAcquire方法尝试获取锁,如果获取锁失败会调用AQS中的acquireQueued方法进行排队、阻塞等处理.
1
2
3
4
5
|
public
final
void
acquire(
int
arg) {
if
(!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
|
下面看下NonFairSync类中重写的tryAcquire方法,里面又调用了nonfairTryAcquire方法 。
1
2
3
|
protected
final
boolean
tryAcquire(
int
acquires) {
return
nonfairTryAcquire(acquires);
}
|
下面看下nonfairTryAcquire方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
final
boolean
nonfairTryAcquire(
int
acquires) {
//acquires = 1
final
Thread current = Thread.currentThread();
int
c = getState();
/**
* 不需要判断同步队列(CLH)中是否有排队等待线程
* 判断state状态是否为0,不为0可以加锁
*/
if
(c ==
0
) {
//unsafe操作,cas修改state状态
if
(compareAndSetState(
0
, acquires)) {
//独占状态锁持有者指向当前线程
setExclusiveOwnerThread(current);
return
true
;
}
}
/**
* state状态不为0,判断锁持有者是否是当前线程,
* 如果是当前线程持有 则state+1
*/
else
if
(current == getExclusiveOwnerThread()) {
int
nextc = c + acquires;
if
(nextc <
0
)
// overflow
throw
new
Error(
"Maximum lock count exceeded"
);
setState(nextc);
return
true
;
}
//加锁失败
return
false
;
}
|
下面看下非公平锁的解锁过程:unlock方法中调用了AQS中的release方法 。
1
2
3
|
public
void
unlock() {
sync.release(
1
);
}
|
AQS中的release方法如下所示:会先调用AQS的子类Sync中重写的tryRelease方法去释放锁,如果是否锁成功,则唤醒同步队列中head的后续节点,后续节点线程被唤醒会去竞争锁.
1
2
3
4
5
6
7
8
9
|
public
final
boolean
release(
int
arg) {
if
(tryRelease(arg)) {
//释放一次锁
Node h = head;
if
(h !=
null
&& h.waitStatus !=
0
)
unparkSuccessor(h);
//唤醒后继结点
return
true
;
}
return
false
;
}
|
Sync中重写的tryRelease方法:
获取当前的state值,然后减1 。
判断当前线程是否是锁的持有线程,如果不是会抛出异常.
如果state的值被减到了0,表示锁已经被释放,会将独占线程设置为空null,将state设置为0,返回true,否则返回false.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/**
* 释放锁
*/
protected
final
boolean
tryRelease(
int
releases) {
int
c = getState() - releases;
if
(Thread.currentThread() != getExclusiveOwnerThread())
throw
new
IllegalMonitorStateException();
boolean
free =
false
;
if
(c ==
0
) {
free =
true
;
setExclusiveOwnerThread(
null
);
}
setState(c);
return
free;
}
|
先看下公平锁的加锁lock方法,lock方法中调用了sync的lock方法,这里调用的是FairSync的lock方法.
1
2
3
|
public
void
lock() {
sync.lock();
}
|
FairSync的lock方法直接调用了AQS中的acquire方法,没有像非公平锁先通过CAS的方式先去尝试获取锁 。
1
2
3
|
final
void
lock() {
acquire(
1
);
}
|
下面看下acquire方法,会先调用FairSync类中重写的tryAcquire方法尝试获取锁,如果获取锁失败会调用AQS中的acquireQueued方法进行排队、阻塞等处理.
1
2
3
4
5
|
public
final
void
acquire(
int
arg) {
if
(!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
|
下面看下FairSync类中重写的tryAcquire方法,这个方法跟NonFairSync的唯一区别就是state为0的时候,公平锁会先通过hasQueuedPredecessors()方法判断是否队列中是否有等待的节点,如果没有才会尝试通过CAS的方式去获取锁,非公平锁不会判断直接回尝试获取锁.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
protected
final
boolean
tryAcquire(
int
acquires) {
final
Thread current = Thread.currentThread();
int
c = getState();
if
(c ==
0
) {
/**
* 与非公平锁中的区别,需要先判断队列当中是否有等待的节点
* 如果没有则可以尝试CAS获取锁
*/
if
(!hasQueuedPredecessors() &&
compareAndSetState(
0
, acquires)) {
//独占线程指向当前线程
setExclusiveOwnerThread(current);
return
true
;
}
}
else
if
(current == getExclusiveOwnerThread()) {
int
nextc = c + acquires;
if
(nextc <
0
)
throw
new
Error(
"Maximum lock count exceeded"
);
setState(nextc);
return
true
;
}
return
false
;
}
|
公平锁的unlock方法与非公平锁的代码一样,这里就不再介绍了.
到此这篇关于Java并发编程之ReentrantLock实现原理及源码剖析的文章就介绍到这了,更多相关Java ReentrantLock内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://blog.csdn.net/u012988901/article/details/112557666 。
最后此篇关于Java并发编程之ReentrantLock实现原理及源码剖析的文章就讲到这里了,如果你想了解更多关于Java并发编程之ReentrantLock实现原理及源码剖析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在编写一个具有以下签名的 Java 方法。 void Logger(Method method, Object[] args); 如果一个方法(例如 ABC() )调用此方法 Logger,它应该
我是 Java 新手。 我的问题是我的 Java 程序找不到我试图用作的图像文件一个 JButton。 (目前这段代码什么也没做,因为我只是得到了想要的外观第一的)。这是我的主课 代码: packag
好的,今天我在接受采访,我已经编写 Java 代码多年了。采访中说“Java 垃圾收集是一个棘手的问题,我有几个 friend 一直在努力弄清楚。你在这方面做得怎么样?”。她是想骗我吗?还是我的一生都
我的 friend 给了我一个谜语让我解开。它是这样的: There are 100 people. Each one of them, in his turn, does the following
如果我将使用 Java 5 代码的应用程序编译成字节码,生成的 .class 文件是否能够在 Java 1.4 下运行? 如果后者可以工作并且我正在尝试在我的 Java 1.4 应用程序中使用 Jav
有关于why Java doesn't support unsigned types的问题以及一些关于处理无符号类型的问题。我做了一些搜索,似乎 Scala 也不支持无符号数据类型。限制是Java和S
我只是想知道在一个 java 版本中生成的字节码是否可以在其他 java 版本上运行 最佳答案 通常,字节码无需修改即可在 较新 版本的 Java 上运行。它不会在旧版本上运行,除非您使用特殊参数 (
我有一个关于在命令提示符下执行 java 程序的基本问题。 在某些机器上我们需要指定 -cp 。 (类路径)同时执行java程序 (test为java文件名与.class文件存在于同一目录下) jav
我已经阅读 StackOverflow 有一段时间了,现在我才鼓起勇气提出问题。我今年 20 岁,目前在我的家乡(罗马尼亚克卢日-纳波卡)就读 IT 大学。足以介绍:D。 基本上,我有一家提供簿记应用
我有 public JSONObject parseXML(String xml) { JSONObject jsonObject = XML.toJSONObject(xml); r
我已经在 Java 中实现了带有动态类型的简单解释语言。不幸的是我遇到了以下问题。测试时如下代码: def main() { def ks = Map[[1, 2]].keySet()
一直提示输入 1 到 10 的数字 - 结果应将 st、rd、th 和 nd 添加到数字中。编写一个程序,提示用户输入 1 到 10 之间的任意整数,然后以序数形式显示该整数并附加后缀。 public
我有这个 DownloadFile.java 并按预期下载该文件: import java.io.*; import java.net.URL; public class DownloadFile {
我想在 GUI 上添加延迟。我放置了 2 个 for 循环,然后重新绘制了一个标签,但这 2 个 for 循环一个接一个地执行,并且标签被重新绘制到最后一个。 我能做什么? for(int i=0;
我正在对对象 Student 的列表项进行一些测试,但是我更喜欢在 java 类对象中创建硬编码列表,然后从那里提取数据,而不是连接到数据库并在结果集中选择记录。然而,自从我这样做以来已经很长时间了,
我知道对象创建分为三个部分: 声明 实例化 初始化 classA{} classB extends classA{} classA obj = new classB(1,1); 实例化 它必须使用
我有兴趣使用 GPRS 构建车辆跟踪系统。但是,我有一些问题要问以前做过此操作的人: GPRS 是最好的技术吗?人们意识到任何问题吗? 我计划使用 Java/Java EE - 有更好的技术吗? 如果
我可以通过递归方法反转数组,例如:数组={1,2,3,4,5} 数组结果={5,4,3,2,1}但我的结果是相同的数组,我不知道为什么,请帮助我。 public class Recursion { p
有这样的标准方式吗? 包括 Java源代码-测试代码- Ant 或 Maven联合单元持续集成(可能是巡航控制)ClearCase 版本控制工具部署到应用服务器 最后我希望有一个自动构建和集成环境。
我什至不知道这是否可能,我非常怀疑它是否可能,但如果可以,您能告诉我怎么做吗?我只是想知道如何从打印机打印一些文本。 有什么想法吗? 最佳答案 这里有更简单的事情。 import javax.swin
我是一名优秀的程序员,十分优秀!