显式锁

上篇讲了使用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并发编程(五)锁的使用(下)的更多相关文章

  1. Java并发编程:锁的释放

    Java并发编程:锁的释放 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} Ja ...

  2. Java并发编程(06):Lock机制下API用法详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.Lock体系结构 1.基础接口简介 Lock加锁相关结构中涉及两个使用广泛的基础API:ReentrantLock类和Condition接 ...

  3. 【Java并发编程五】信号量

    一.概述 技术信号量用来控制能够同时访问某特定资源的活动的数量,或者同时执行某一给定操作的数据.计数信号量可以用来实现资源池或者给一个容器限定边界. 信号量维护了一个许可集,许可的初始量通过构造函数传 ...

  4. Java并发编程之锁机制

    锁分类 悲观锁与乐观锁 悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改.因此对于同一个数据的并发操作,悲观锁采取加锁的形式.悲观的认为,不加锁的并发操作一定会出问题 ...

  5. java并发编程:锁的相关概念介绍

    理解同步,最好先把java中锁相关的概念弄清楚,有助于我们更好的去理解.学习同步.java语言中与锁有关的几个概念主要是:可重入锁.读写锁.可中断锁.公平锁 一.可重入锁 synchronized和R ...

  6. Java并发编程-各种锁

    安全性和活跃度通常相互牵制.我们使用锁来保证线程安全,但是滥用锁可能引起锁顺序死锁.类似地,我们使用线程池和信号量来约束资源的使用, 但是缺不能知晓哪些管辖范围内的活动可能形成的资源死锁.Java应用 ...

  7. Java并发编程 (五) 线程安全性

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.安全发布对象-发布与逸出 1.发布与逸出定义 发布对象 : 使一个对象能够被当前范围之外的代码所使用 ...

  8. 【Java并发编程】并发编程大合集-值得收藏

    http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用 ...

  9. 【Java并发编程】并发编程大合集

    转载自:http://blog.csdn.net/ns_code/article/details/17539599 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅 ...

  10. Java并发编程原理与实战十五:手动实现一个可重入锁

     package com.roocon.thread.ta1; public class Sequence { private MyLock lock = new MyLock(); private ...

随机推荐

  1. 14.QT-QFile文件,QBuffer缓冲区,QDir目录,QFileSystemWatcher文件系统监视

    QFile Qt中所有与IO相关的类都继承于QIODevice,继承图如下所示: 其中QFile类便是用于文件操作的类 在QT中,将文件当做一种特殊的外部设备对待(比如:串口,usb等就是外部设备) ...

  2. mybatis中autoCommit自动提交事务

    今天学习了下mybatis, 对其中的autoCommit自动提交事务比较好奇, 研究了下,把配置和代码都放上 mapper.xml如下: <?xml version="1.0&quo ...

  3. CSS基础:替换元素和非替换元素

    简介 根据 "外在盒子" 是内联还是块级我们可以把元素分为内联元素和块级元素,而根据是否具有可替换内容,我们也可以把元素分为替换元素和非替换元素.这种通过修改某个属性值,例如 &l ...

  4. jquery性能优化的38个建议

    一.注意定义jQuery变量的时候添加var关键字 这个不仅仅是jQuery,所有javascript开发过程中,都需要注意,请一定不要定义成如下: $loading = $('#loading'); ...

  5. 北京一家JAVA开发公司面试题(留给后人)

    1.jsp有哪些内置对象?作用分别是什么? 2.描述一下servlet的生命周期和基本架构. 3.多线程有几种实现方法,都是什么? 同步有几种实现方法,都是什么? 4.作用域public   priv ...

  6. sublime使用package control安装插件

    sublime本身可以集成Package Control来进行插件安装,非常方便. 1. 安装package control(插件包管理) 1.1 进入https://packagecontrol.i ...

  7. JavaScript验证和数据处理的干货(经典)

    在开发web项目的时候,难免遇到各种对网页数据的处理,比如对用户在表单中输入的电话号码.邮箱.金额.身份证号.密码长度和复杂程度等等的验证,以及对后台返回数据的格式化比如金额,返回的值为null,还有 ...

  8. 算法竞赛之递归——输出1-n的所有排列

    本文是博主原创文章,未经允许不得转载.我的csdn博客也同步发布了此文, 链接 https://blog.csdn.net/umbrellalalalala/article/details/79792 ...

  9. Coursera-AndrewNg(吴恩达)机器学习笔记——第二周编程作业

    一.准备工作 从网站上将编程作业要求下载解压后,在Octave中使用cd命令将搜索目录移动到编程作业所在目录,然后使用ls命令检查是否移动正确.如: 提交作业:提交时候需要使用自己的登录邮箱和提交令牌 ...

  10. Oracle入门《Oracle介绍》第一章1-3 Oracle 逻辑组件

    一.数据库的逻辑结构是从逻辑的角度分析数据库的组成.Oracle 的逻辑组件包括: 1.表空间 表空间是数据库中最大的逻辑单位,一个 Oracle 数据库至少包含一个表空间,就是名为SYSTEM的系统 ...