Java并发编程(五)锁的使用(下)
显式锁
上篇讲了使用synchronized关键字来定义锁,其实Java除了使用这个关键字外还可以使用Lock接口及其实现的子类来定义锁,ReentrantLock类是Lock接口的一个实现,Reentrant是“重入”的意思,因此这个锁也是支持重入的,这里就不再测试它的重入性了,感兴趣的同学可以自行测试。这种方式是Java在jdk1.5才引入进来的功能,它的功能比synchronized关键字更为强大,但也有一个缺点:使用起来比synchronized关键字更麻烦。使用Lock来实现value++,代码如下:
class Entity {
public static int value = 0;
}
class IncreaseThread implements Runnable {
private static Lock lock = new ReentrantLock();
public void run() {
for(int i=0; i <100000; i++){
lock.lock();
try {
Entity.value++;
}
finally {
lock.unlock();
}
}
}
}
public class ReentrantLockTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new IncreaseThread());
exec.execute(new IncreaseThread());
exec.shutdown();
Thread.sleep(5000);
System.out.println("Value = " + Entity.value);
}
}
运行5秒后输出如下结果:
Value = 200000
在定义lock时将其声明为static的,因此两个IncreaseThread对象可以共用一个lock对象;如果使用的不是同一个对象,尝试获取的就不是同一个锁了,也就不会互斥。代码执行到lock()方法时会检查锁是否被占用,如果没有被占用则直接获取锁并执行下面的代码,如果已经被占用了则阻塞当前线程,等待锁被其他线程释放。使用lock设置的临界区从调用lock()方法开始,到调用unlock()方法结束,本例在try块中定义要执行的代码,在finally块中调用unlock()方法是一种常用的方式,这样即使要执行的代码中抛出异常,也可以保证锁会被正常释放。
试图获取锁
tryLock方法给我们提供了两种尝试获取锁的方式,即根据是否获取到了锁来执行不同的策略,我们先介绍两个方法的功能。
tryLock():试图获取锁,如果锁没有被占用则得到锁、返回true、继续执行下面的代码;如果锁被占用了则立即返回false(不会被阻塞)、继续执行下面的代码。
tryLock(long time, TimeUnit unit):与tryLock()类似,这个方法可以指定等待锁的最长时间,在这段时间内当前线程会被阻塞。如果时间内获得了锁则返回true并提前结束阻塞,反之返回false。
具体代码如下:
class TryToGetLockThread implements Runnable {
public void run() {
boolean alreadyGetLock = TryLockTest.lock.tryLock();
if(alreadyGetLock) {
try {
System.out.println("新线程:我拿到锁了");
Thread.sleep(3000);//持有锁3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
System.out.println("新线程:我要释放锁了 ");
TryLockTest.lock.unlock();
}
}
}
}
public class TryLockTest {
public static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new TryToGetLockThread());
exec.shutdown();
boolean alreadyGetLock = lock.tryLock(2, TimeUnit.SECONDS);
if(alreadyGetLock) {
try {
System.out.println("主线程:我拿到锁了");
}
finally {
TryLockTest.lock.unlock();
}
}
else {
System.out.println("主线程:没拿到锁,可以做点别的事情");
}
}
}
运行程序后,输出结果如下,其中第一条是立即输出的,第二句是两秒之后输出的,第三句是三秒后输出的:
新线程:我拿到锁了
主线程:没拿到锁,可以做点别的事情
新线程:我要释放锁了
本例中我们创建了一个新线程,新线程使用tryLock()方法先获得了锁,持有这个锁3秒钟。主线程使用tryLock(long time, TimeUnit unit)方法来试图获取锁,由于新线程持有锁3秒,因此主线程执行lock.tryLock(2, TimeUnit.SECONDS)等待两秒后就放弃了获取锁,返回false;如果主线程等待4秒,那么它就可以得到锁,从而得到不同的结果,感兴趣的同学可以自行测试。
锁的对象是谁
有的同学可能觉得这个锁对应的是这个锁的对象(lock),实际上不是的,这个锁没有对应的锁对象,因此synchronized关键字和显式锁之间不能产生互斥效果。
让我们做一个测试:
class LockTest {
private static Lock lock = new ReentrantLock();
public static void neverStopMethod() {
lock.lock();
try {
while(true){}//Never stop
}
finally{
lock.unlock();
}
}
public static void getLockMethod() {
synchronized(lock) {
System.out.println("我得到了class锁");
}
}
}
class NeverStopThread implements Runnable {
public void run() {
LockTest.neverStopMethod();
}
}
public class TryToGetSameLock {
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new NeverStopThread());
exec.shutdown();
Thread.sleep(100);//等待0.1秒,确保新建的线程已经获得了锁
LockTest.getLockMethod();
}
}
程序运行后,立即输出以下结果,并且程序始终没有退出
我得到了class锁
neverStopMethod()方法使用显式锁,getLockMethod()使用内置锁来获取lock对象的锁,我们通过线程池创建了一个调用neverStopMethod()方法的线程,主线程等待0.1秒后再通过getLockMethod()方法来获取锁。如果这两个锁是互斥的,getLockMethod()会一直等待新线程释放锁,但是很遗憾,它没有等待而是直接获取了锁。
锁的公平性
公平锁:按照访问锁的先后顺序排队,先到先得,这个很好理解。
非公平锁:当一个线程想获取锁时,先试图插队,如果刚好占用锁的线程释放了锁,下一个线程还没来得及拿锁,那么当前线程就可以直接获得锁;如果锁正在被其它线程占用,则排队,排队的时候就不能再试图获得锁了,只能等到前面所有线程都执行完才能获得锁。
synchronized关键字和默认情况下的ReentrantLock都是非公平锁。非公平锁比公平锁的性能更好,假设线程一是队列中的第一个,线程二是想要插队的线程,当占用锁的线程释放了锁时JVM有两种选择:
1.阻塞线程二,启动线程一。
2.线程一的状态维持不变继续阻塞,线程二的状态也维持不变继续运行。线程状态的切换是耗费时间的,因此方案二的性能更好。
ReentrantLock类默认的构造方法是非公平锁,如果需要设置为非公平锁,只需要调用ReentrantLock(boolean fair)方法指定即可。
总结
最后我们来盘点一下synchronized关键字和ReentrantLock类各自的优势:
synchronized关键字的优势:
使用简单,不用显示释放锁,编写代码更简洁;ReentrantLock类需要显示释放锁,unlock()方法要写在finally块中,否则会有死锁的风险。
ReentrantLock类的优势:
提供了更多的特性,比如设置锁的公平性,检查锁是否被占用等功能;synchronized关键字只能是公平锁,并且没有额外的特性。
一般来讲我们不需要额外功能的时候才会使用ReentrantLock类,否则都是使用synchronized关键字。
公众号:今日说码。关注我的公众号,可查看连载文章。遇到不理解的问题,直接在公众号留言即可。
Java并发编程(五)锁的使用(下)的更多相关文章
- Java并发编程:锁的释放
Java并发编程:锁的释放 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} Ja ...
- Java并发编程(06):Lock机制下API用法详解
本文源码:GitHub·点这里 || GitEE·点这里 一.Lock体系结构 1.基础接口简介 Lock加锁相关结构中涉及两个使用广泛的基础API:ReentrantLock类和Condition接 ...
- 【Java并发编程五】信号量
一.概述 技术信号量用来控制能够同时访问某特定资源的活动的数量,或者同时执行某一给定操作的数据.计数信号量可以用来实现资源池或者给一个容器限定边界. 信号量维护了一个许可集,许可的初始量通过构造函数传 ...
- Java并发编程之锁机制
锁分类 悲观锁与乐观锁 悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改.因此对于同一个数据的并发操作,悲观锁采取加锁的形式.悲观的认为,不加锁的并发操作一定会出问题 ...
- java并发编程:锁的相关概念介绍
理解同步,最好先把java中锁相关的概念弄清楚,有助于我们更好的去理解.学习同步.java语言中与锁有关的几个概念主要是:可重入锁.读写锁.可中断锁.公平锁 一.可重入锁 synchronized和R ...
- Java并发编程-各种锁
安全性和活跃度通常相互牵制.我们使用锁来保证线程安全,但是滥用锁可能引起锁顺序死锁.类似地,我们使用线程池和信号量来约束资源的使用, 但是缺不能知晓哪些管辖范围内的活动可能形成的资源死锁.Java应用 ...
- Java并发编程 (五) 线程安全性
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.安全发布对象-发布与逸出 1.发布与逸出定义 发布对象 : 使一个对象能够被当前范围之外的代码所使用 ...
- 【Java并发编程】并发编程大合集-值得收藏
http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用 ...
- 【Java并发编程】并发编程大合集
转载自:http://blog.csdn.net/ns_code/article/details/17539599 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅 ...
- Java并发编程原理与实战十五:手动实现一个可重入锁
package com.roocon.thread.ta1; public class Sequence { private MyLock lock = new MyLock(); private ...
随机推荐
- 解决 RabbitMQ 集群 Channel shutdown: connection error 错误(HAProxy 负载均衡)
相关文章:搭建 RabbitMQ Server 高可用集群 具体错误信息: 2018-05-04 11:21:48.116 ERROR 60848 --- [.168.0.202:8001] o.s. ...
- Mysql创建索引
1.索引作用 在索引列上,除了上面提到的有序查找之外,数据库利用各种各样的快速定位技术,能够大大提高查询效率.特别是当数据量非常大,查询涉及多个表时,使用索引往往能使查询速度加快成千上万倍. 例如,有 ...
- TCP TIME WAIT
根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方 socket将进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(Max Segment Lifetime) ...
- 深入理解SpringCloud之配置刷新
我们知道在SpringCloud中,当配置变更时,我们通过访问http://xxxx/refresh,可以在不启动服务的情况下获取最新的配置,那么它是如何做到的呢,当我们更改数据库配置并刷新后,如何能 ...
- https证书链不完整
公司的一个域名,用浏览器打开能正常访问,但是在linux下使用curl命令,总是报错,报错信息如下: curl: (60) Peer certificate cannot be authenticat ...
- Golang中WaitGroup使用的一点坑
Golang中WaitGroup使用的一点坑 Golang 中的 WaitGroup 一直是同步 goroutine 的推荐实践.自己用了两年多也没遇到过什么问题.直到一天午睡后,同事扔过来一段奇怪的 ...
- 【转】学习FFmpeg API – 解码视频
ffmpeg是编解码的利器,用了很久,以前看过dranger 的教程,非常精彩,受益颇多,是学习ffmpeg api很好的材料.可惜的是其针对的ffmpeg版本已经比较老了,而ffmpeg的更新又很快 ...
- 对cordova插件配置文件plugin.xml的理解
1.配置文件表头包括了插件id,是用于唯一标识插件的.同时插件配置了一个插件名称. 2.这个文件从工作机制,也就是js代码一直到native的java插件代码工作分成两个流程.第一个流程是从代码到插件 ...
- 搜索应用参考示例XXL-SEARCH
<搜索应用参考示例XXL-SEARCH> 一.简介 1.1 概述 XXL-SEARCH 是以 "lucene/elasticsearch" 为核心的,Pragmatic ...
- jasperReport Studio java报表设计(详细)
一.环境搭建 在spring-mvc.xml加入 <!-- jasperReports--><import resource="classpath*:spring-mvc- ...