synchronized and Reentrantlock

多线程编程中,当代码需要同步时我们会用到锁。Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两种同步方式。显式锁是JDK1.5引入的,这两种锁有什么异同呢?是仅仅增加了一种选择还是另有其因?本文为您一探究竟。

内置锁

Java内置锁通过synchronized关键字使用,使用其修饰方法或者代码块,就能保证方法或者代码块以同步方式执行。使用起来非常近简单,就像下面这样:

// synchronized关键字用法示例
public synchronized void add(int t){// 同步方法
this.v += t;
} public static synchronized void sub(int t){// 同步静态方法
value -= t;
}
public int decrementAndGet(){
synchronized(obj){// 同步代码块
return --v;
}
}

这就是内置锁的全部用法,你已经学会了。

内置锁使用起来非常方便,不需要显式的获取和释放,任何一个对象都能作为一把内置锁。使用内置锁能够解决大部分的同步场景。“任何一个对象都能作为一把内置锁”也意味着出现synchronized关键字的地方,都有一个对象与之关联,具体说来:

  • 当synchronized作用于普通方法是,锁对象是this;
  • 当synchronized作用于静态方法是,锁对象是当前类的Class对象;
  • 当synchronized作用于代码块时,锁对象是synchronized(obj)中的这个obj。

显式锁

内置锁这么好用,为什么还需多出一个显式锁呢?因为有些事情内置锁是做不了的,比如:

  1. 我们想给锁加个等待时间超时时间,超时还未获得锁就放弃,不至于无限等下去;
  2. 我们想以可中断的方式获取锁,这样外部线程给我们发一个中断信号就能唤起等待锁的线程;
  3. 我们想为锁维持多个等待队列,比如一个生产者队列,一个消费者队列,一边提高锁的效率。

显式锁(ReentrantLock)正式为了解决这些灵活需求而生。ReentrantLock的字面意思是可重入锁,可重入的意思是线程可以同时多次请求同一把锁,而不会自己导致自己死锁。下面是内置锁和显式锁的区别:

  • 可定时:RenentrantLock.tryLock(long timeout, TimeUnit unit)提供了一种以定时结束等待的方式,如果线程在指定的时间内没有获得锁,该方法就会返回false并结束线程等待。

  • 可中断:你一定见过InterruptedException,很多跟多线程相关的方法会抛出该异常,这个异常并不是一个缺陷导致的负担,而是一种必须,或者说是一件好事。可中断性给我们提供了一种让线程提前结束的方式(而不是非得等到线程执行结束),这对于要取消耗时的任务非常有用。对于内置锁,线程拿不到内置锁就会一直等待,除了获取锁没有其他办法能够让其结束等待。RenentrantLock.lockInterruptibly()给我们提供了一种以中断结束等待的方式。

  • 条件队列(condition queue):线程在获取锁之后,可能会由于等待某个条件发生而进入等待状态(内置锁通过Object.wait()方法,显式锁通过Condition.await()方法),进入等待状态的线程会挂起并自动释放锁,这些线程会被放入到条件队列当中。synchronized对应的只有一个条件队列,而ReentrantLock可以有多个条件队列,多个队列有什么好处呢?请往下看。

  • 条件谓词:线程在获取锁之后,有时候还需要等待某个条件满足才能做事情,比如生产者需要等到“缓存不满”才能往队列里放入消息,而消费者需要等到“缓存非空”才能从队列里取出消息。这些条件被称作条件谓词,线程需要先获取锁,然后判断条件谓词是否满足,如果不满足就不往下执行,相应的线程就会放弃执行权并自动释放锁。使用同一把锁的不同的线程可能有不同的条件谓词,如果只有一个条件队列,当某个条件谓词满足时就无法判断该唤醒条件队列里的哪一个线程;但是如果每个条件谓词都有一个单独的条件队列,当某个条件满足时我们就知道应该唤醒对应队列上的线程(内置锁通过Object.notify()或者Object.notifyAll()方法唤醒,显式锁通过Condition.signal()或者Condition.signalAll()方法唤醒)。这就是多个条件队列的好处。

使用内置锁时,对象本身既是一把锁又是一个条件队列;使用显式锁时,RenentrantLock的对象是锁,条件队列通过RenentrantLock.newCondition()方法获取,多次调用该方法可以得到多个条件队列。

一个使用显式锁的典型示例如下:

// 显式锁的使用示例
ReentrantLock lock = new ReentrantLock(); // 获取锁,这是跟synchronized关键字对应的用法。
lock.lock();
try{
// your code
}finally{
lock.unlock();
} // 可定时,超过指定时间为得到锁就放弃
try {
lock.tryLock(10, TimeUnit.SECONDS);
try {
// your code
}finally {
lock.unlock();
}
} catch (InterruptedException e1) {
// exception handling
} // 可中断,等待获取锁的过程中线程线程可被中断
try {
lock.lockInterruptibly();
try {
// your code
}finally {
lock.unlock();
}
} catch (InterruptedException e) {
// exception handling
} // 多个等待队列,具体参考[ArrayBlockingQueue](https://github.com/CarpenterLee/JCRecipes/blob/master/markdown/ArrayBlockingQueue.md)
/** Condition for waiting takes */
private final Condition notEmpty = lock.newCondition();
/** Condition for waiting puts */
private final Condition notFull = lock.newCondition();

注意,上述代码将unlock()放在finally块里,这么做是必需的。显式锁不像内置锁那样会自动释放,使用显式锁一定要在finally块中手动释放,如果获取锁后由于异常的原因没有释放锁,那么这把锁将永远得不到释放!将unlock()放在finally块中,保证无论发生什么都能够正常释放。

结论

内置锁能够解决大部分需要同步的场景,只有在需要额外灵活性是才需要考虑显式锁,比如可定时、可中断、多等待队列等特性。

显式锁虽然灵活,但是需要显式的申请和释放,并且释放一定要放到finally块中,否则可能会因为异常导致锁永远无法释放!这是显式锁最明显的缺点。

综上,当需要同步时请优先考虑更安全的更易用的隐式锁。

参考文献

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantLock.html

深入理解Java内置锁和显式锁的更多相关文章

  1. 深入理解java内置锁(synchronized)和显式锁(ReentrantLock)

    多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两种同步方式.显式锁是JDK1.5引入的,这两种锁有什么异同呢? ...

  2. Java编程的逻辑 (71) - 显式锁

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  3. Java并发编程系列-(4) 显式锁与AQS

    4 显示锁和AQS 4.1 Lock接口 核心方法 Java在java.util.concurrent.locks包中提供了一系列的显示锁类,其中最基础的就是Lock接口,该接口提供了几个常见的锁相关 ...

  4. Java 实现一个自己的显式锁Lock(有超时功能)

    Lock接口 package concurency.chapter9; import java.util.Collection; public interface Lock { static clas ...

  5. java 并发多线程显式锁概念简介 什么是显式锁 多线程下篇(一)

    目前对于同步,仅仅介绍了一个关键字synchronized,可以用于保证线程同步的原子性.可见性.有序性 对于synchronized关键字,对于静态方法默认是以该类的class对象作为锁,对于实例方 ...

  6. Java并发-显式锁篇【可重入锁+读写锁】

    作者:汤圆 个人博客:javalover.cc 前言 在前面并发的开篇,我们介绍过内置锁synchronized: 这节我们再介绍下显式锁Lock 显式锁包括:可重入锁ReentrantLock.读写 ...

  7. 六、显式锁和AQS

    显式锁和AQS 一.显式锁 ​ Synchronized 关键字结合对象的监视器,JVM 为我们提供了一种『内置锁』的语义,这种锁很简便,不需要我们关心加锁和释放锁的过程,我们只需要告诉虚拟机哪些代码 ...

  8. 显式锁(二)Lock接口与显示锁介绍

    一.显式锁简介    显式锁,这个叫法是相对于隐式锁synchronized而言的,加锁和解锁都要用户显式地控制.显示锁Lock是在Java5中添加到jdk的,同synchronized一样,这也是一 ...

  9. Java内置锁和简单用法

    一.简单的锁知识 关于内置锁 Java具有通过synchronized关键字实现的内置锁,内置锁获得锁和释放锁是隐式的,进入synchronized修饰的代码就获得锁,走出相应的代码就释放锁. jav ...

随机推荐

  1. 【源码】canal和otter的高可靠性分析

    一般来说,我们对于数据库最主要的要求就是:数据不丢.不管是主从复制,还是使用类似otter+canal这样的数据库同步方案,我们最基本的需求是,在数据不丢失的前提下,尽可能的保证系统的高可用,也就是在 ...

  2. 【转载】CSS3的calc()使用

    文章转载自 w3cplus http://www.w3cplus.com/ 原文链接:http://www.w3cplus.com/css3/how-to-use-css3-calc-function ...

  3. 框架应用 : Spring MVC - 开发详述

    软件开发中的MVC设计模式 软件开发的目标是减小耦合,让模块之前关系清晰. MVC模式在软件开发中经常和ORM模式一起应用,主要作用是将(数据抽象,数据实体传输和前台数据展示)分层,这样前台,后台,数 ...

  4. mac 通过brew安装php70 +php-fpm+ phalcon3.0.3

    安装php7.0.15 brew install homebrew/php/php70 brew install homebrew/php/php70-mcrypt brew install home ...

  5. [js高手之路]html5 canvas动画教程 - 下雪效果

    利用canvas,实现一个下雪的效果,我们先预览下效果: 我们先分析下这个效果: 1,随机产生雪花 2,雪花的产生不是同时产生,而是有先后顺序的 3,雪花怎么表示 4,怎么源源不断的下雪 5,雪花有大 ...

  6. Xamarin Forms 进度条控件

    本文翻译:http://xamlnative.com/2016/04/14/xamarin-forms-a-simple-circular-progress-control/ 里面都是胡说的,如果看不 ...

  7. 利用Dropbox同步Arch下一些软件的配置文件

    一些软件后要好好的配置一番才能好好为自己所用,比如vim,比如conky等等,但是万一系统真出了问题,或者硬盘跪了,要重新安装系统重新配置的时候真是一种折磨,前阵子想到如果用dropbox同步那些配置 ...

  8. 本地连接 vmware服务器

    在本机中装载虚拟机,安装redhat.需要调试使用redhat可以与Windows进行通讯. 分为多步,在此前提下,默认你已经安装好且可以vm 和虚拟机 1:点击虚拟机>设置>添加网络适配 ...

  9. Java基础总结--变量、运算符总结

    ---变量基本概述----变量作用:就是用来存储数据变量的声明:变量类型 变量名;变量类型---区分不同类型的数据,变量名--按名访问变量变量名(规则):见名识意,第一个字母小写后面驼峰规则变量的作用 ...

  10. 利用cookies+requests包登陆微博,使用xpath抓取目标用户的用户信息、微博以及对应评论

    本文目的:介绍如何抓取微博内容,利用requests包+cookies实现登陆微博,lxml包的xpath语法解析网页,抓取目标内容. 所需python包:requests.lxml 皆使用pip安装 ...