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 ...
随机推荐
- day09_request&response学习笔记
============================================================ 一.HttpServletResponse接口 p.MsoNormal { m ...
- 排序算法入门之快速排序(java实现)
快速排序也是一种分治的排序算法.快速排序和归并排序是互补的:归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序,会需要一个额外的数组:而快速排序的排序方式是当两个子数组都有序时 ...
- OpenStack初识
一.它可以用来做什么? 想认识一个事物,必须先弄明白它是什么,能干什么.首先说一下,openstack是一个搭建云平台的一个解决方案,说他不是个软件,但是我觉得说是一个软件,能够让初学者更容易接受和理 ...
- java——封装和关键字
封装:将类的属性和方法的实现细节隐藏起来的过程 封装的好处:1重用性(代码)2,利于分工3,隐藏细节 访问关键字:public private 默认访问修饰符,protected static关键字 ...
- jbpm 工作流(二)
1 概述 本文主要介绍如何将JBPM+Struts+Spring+Hibernate整合在一块.并通过一个简单实例来说明.此实例为一个申请审批的简单流程,并将申请人和审批人记录到数 ...
- 自动红眼移除算法 附c++完整代码
说起红眼算法,这个话题非常古老了. 百度百科上的描述: "红眼"一般是指在人物摄影时,当闪光灯照射到人眼的时候,瞳孔放大而产生的视网膜泛红现象. 由于红眼现象的程度是根据拍摄对象色 ...
- Airbnb/Apache Superset – the open source dashboards and visualization tool – first impressions and link to a demo
https://assemblinganalytics.com/post/airbnbapache-superset-first-impressions-and-link-to-a-demo/ Tod ...
- Django-CKedtior图片找不到的问题
从Django Packages站点上找到这个CKeditor集成组件:https://github.com/shaunsephton/django-ckeditor 按照官方的install方法安装 ...
- AngularJS + RequireJS
http://www.startersquad.com/blog/AngularJS-requirejs/ While delivering software projects for startup ...
- Python入门指南(超详细)
Python 是一门非常容易上手的语言,通过查阅资料和教程,也许一晚上就能写出一个简单的爬虫.但 Python 也是一门很难精通的语言,因为简洁的语法背后隐藏了许多黑科技.本文主要针对的读者是: 毫无 ...