简单使用Lock锁

Java5中引入了新的锁机制——Java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作。Lock接口有3个实现它的类:ReentrantLock、ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。lock必须被显式地创建、锁定和释放,为了可以使用更多的功能,一般用ReentrantLock为其实例化。为了保证锁最终一定会被释放(可能会有异常发生),要把互斥区放在try语句块内,并在finally语句块中释放锁,尤其当有return语句时,return语句必须放在try字句中,以确保unlock()不会过早发生,从而将数据暴露给第二个任务。因此,采用lock加锁和释放锁的一般形式如下:

  1. Lock lock = new ReentrantLock();//默认使用非公平锁,如果要使用公平锁,需要传入参数true
  2. ........
  3. lock.lock();
  4. try {
  5. //更新对象的状态
  6. //捕获异常,必要时恢复到原来的不变约束
  7. //如果有return语句,放在这里
  8. finally {
  9. lock.unlock();        //锁必须在finally块中释放

ReetrankLock与synchronized比较

性能比较

在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。Brian

Goetz对这两种锁在JDK1.5、单核处理器及双Xeon处理器环境下做了一组吞吐量对比的实验,发现多线程环境下,synchronized的吞吐量下降的非常严重,而ReentrankLock则能基本保持在同一个比较稳定的水平上。但与其说ReetrantLock性能好,倒不如说synchronized还有非常大的优化余地,于是到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

下面浅析以下两种锁机制的底层的实现策略。

互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因而这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。synchronized采用的便是这种并发策略。

随着指令集的发展,我们有了另一种选择:基于冲突检测的乐观并发策略,通俗地讲就是先进性操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据被争用,产生了冲突,那就再进行其他的补偿措施(最常见的补偿措施就是不断地重拾,直到试成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步被称为非阻塞同步。ReetrantLock采用的便是这种并发策略。

在乐观的并发策略中,需要操作和冲突检测这两个步骤具备原子性,它靠硬件指令来保证,这里用的是CAS操作(Compare and
Swap)。JDK1.5之后,Java程序才可以使用CAS操作。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState,这里其实就是调用的CPU提供的特殊指令。现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而compareAndSet()
就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起。

Java
5中引入了注入AutomicInteger、AutomicLong、AutomicReference等特殊的原子性变量类,它们提供的如:compareAndSet()、incrementAndSet()和getAndIncrement()等方法都使用了CAS操作。因此,它们都是由硬件指令来保证的原子方法。

用途比较

基本语法上,ReentrantLock与synchronized很相似,它们都具备一样的线程重入特性,只是代码写法上有点区别而已,一个表现为API层面的互斥锁(Lock),一个表现为原生语法层面的互斥锁(synchronized)。ReentrantLock相对synchronized而言还是增加了一些高级功能,主要有以下三项:

1、等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情,它对处理执行时间非常上的同步块很有帮助。而在等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。

2、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁时非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过构造方法ReentrantLock(ture)来要求使用公平锁。

3、锁可以绑定多个条件:ReentrantLock对象可以同时绑定多个Condition对象(名曰:条件变量或条件队列),而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含条件,但如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无需这么做,只需要多次调用newCondition()方法即可。而且我们还可以通过绑定Condition对象来判断当前线程通知的是哪些线程(即与Condition对象绑定在一起的其他线程)。

可中断锁

ReetrantLock有两种锁:忽略中断锁和响应中断锁。忽略中断锁与synchronized实现的互斥锁一样,不能响应中断,而响应中断锁可以响应中断。

如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,如果此时ReetrantLock提供的是忽略中断锁,则它不会去理会该中断,而是让线程B继续等待,而如果此时ReetrantLock提供的是响应中断锁,那么它便会处理中断,让线程B放弃等待,转而去处理其他事情。

获得响应中断锁的一般形式如下:

  1. ReentrantLock lock = new ReentrantLock();
  2. ...........
  3. lock.lockInterruptibly();//获取响应中断锁
  4. try {
  5. //更新对象的状态
  6. //捕获异常,必要时恢复到原来的不变约束
  7. //如果有return语句,放在这里
  8. }finally{
  9. lock.unlock();        //锁必须在finally块中释放
  10. }

这里有一个不错的分析中断的示例代码(摘自网上)

当用synchronized中断对互斥锁的等待时,并不起作用,该线程依然会一直等待,如下面的实例:

  1. public class Buffer {
  2. private Object lock;
  3. public Buffer() {
  4. lock = this;
  5. }
  6. public void write() {
  7. synchronized (lock) {
  8. long startTime = System.currentTimeMillis();
  9. System.out.println("开始往这个buff写入数据…");
  10. for (;;)// 模拟要处理很长时间
  11. {
  12. if (System.currentTimeMillis()
  13. - startTime > Integer.MAX_VALUE) {
  14. break;
  15. }
  16. }
  17. System.out.println("终于写完了");
  18. }
  19. }
  20. public void read() {
  21. synchronized (lock) {
  22. System.out.println("从这个buff读数据");
  23. }
  24. }
  25. public static void main(String[] args) {
  26. Buffer buff = new Buffer();
  27. final Writer writer = new Writer(buff);
  28. final Reader reader = new Reader(buff);
  29. writer.start();
  30. reader.start();
  31. new Thread(new Runnable() {
  32. @Override
  33. public void run() {
  34. long start = System.currentTimeMillis();
  35. for (;;) {
  36. //等5秒钟去中断读
  37. if (System.currentTimeMillis()
  38. - start > 5000) {
  39. System.out.println("不等了,尝试中断");
  40. reader.interrupt();  //尝试中断读线程
  41. break;
  42. }
  43. }
  44. }
  45. }).start();
  46. // 我们期待“读”这个线程能退出等待锁,可是事与愿违,一旦读这个线程发现自己得不到锁,
  47. // 就一直开始等待了,就算它等死,也得不到锁,因为写线程要21亿秒才能完成 T_T ,即使我们中断它,
  48. // 它都不来响应下,看来真的要等死了。这个时候,ReentrantLock给了一种机制让我们来响应中断,
  49. // 让“读”能伸能屈,勇敢放弃对这个锁的等待。我们来改写Buffer这个类,就叫BufferInterruptibly吧,可中断缓存。
  50. }
  51. }
  52. class Writer extends Thread {
  53. private Buffer buff;
  54. public Writer(Buffer buff) {
  55. this.buff = buff;
  56. }
  57. @Override
  58. public void run() {
  59. buff.write();
  60. }
  61. }
  62. class Reader extends Thread {
  63. private Buffer buff;
  64. public Reader(Buffer buff) {
  65. this.buff = buff;
  66. }
  67. @Override
  68. public void run() {
  69. buff.read();//这里估计会一直阻塞
  70. System.out.println("读结束");
  71. }
  72. }

执行结果如下:

    我们等待了很久,后面依然没有输出,说明读线程对互斥锁的等待并没有被中断,也就是该户吃锁没有响应对读线程的中断。
    我们再将上面代码中synchronized的互斥锁改为ReentrantLock的响应中断锁,即改为如下代码:
  1. import java.util.concurrent.locks.ReentrantLock;
  2. public class BufferInterruptibly {
  3. private ReentrantLock lock = new ReentrantLock();
  4. public void write() {
  5. lock.lock();
  6. try {
  7. long startTime = System.currentTimeMillis();
  8. System.out.println("开始往这个buff写入数据…");
  9. for (;;)// 模拟要处理很长时间
  10. {
  11. if (System.currentTimeMillis()
  12. - startTime > Integer.MAX_VALUE) {
  13. break;
  14. }
  15. }
  16. System.out.println("终于写完了");
  17. } finally {
  18. lock.unlock();
  19. }
  20. }
  21. public void read() throws InterruptedException {
  22. lock.lockInterruptibly();// 注意这里,可以响应中断
  23. try {
  24. System.out.println("从这个buff读数据");
  25. } finally {
  26. lock.unlock();
  27. }
  28. }
  29. public static void main(String args[]) {
  30. BufferInterruptibly buff = new BufferInterruptibly();
  31. final Writer2 writer = new Writer2(buff);
  32. final Reader2 reader = new Reader2(buff);
  33. writer.start();
  34. reader.start();
  35. new Thread(new Runnable() {
  36. @Override
  37. public void run() {
  38. long start = System.currentTimeMillis();
  39. for (;;) {
  40. if (System.currentTimeMillis()
  41. - start > 5000) {
  42. System.out.println("不等了,尝试中断");
  43. reader.interrupt();  //此处中断读操作
  44. break;
  45. }
  46. }
  47. }
  48. }).start();
  49. }
  50. }
  51. class Reader2 extends Thread {
  52. private BufferInterruptibly buff;
  53. public Reader2(BufferInterruptibly buff) {
  54. this.buff = buff;
  55. }
  56. @Override
  57. public void run() {
  58. try {
  59. buff.read();//可以收到中断的异常,从而有效退出
  60. } catch (InterruptedException e) {
  61. System.out.println("我不读了");
  62. }
  63. System.out.println("读结束");
  64. }
  65. }
  66. class Writer2 extends Thread {
  67. private BufferInterruptibly buff;
  68. public Writer2(BufferInterruptibly buff) {
  69. this.buff = buff;
  70. }
  71. @Override
  72. public void run() {
  73. buff.write();
  74. }
  75. }

执行结果如下:

    从结果中可以看出,尝试中断后输出了catch语句块中的内容,也输出了后面的“读结束”,说明线程对互斥锁的等待被中断了,也就是该互斥锁响应了对读线程的中断。

条件变量实现线程间协作

在下面将一文中的代码改为用条件变量实现,如下:

  1. import java.util.concurrent.*;
  2. import java.util.concurrent.locks.*;
  3. class Info{ // 定义信息类
  4. private String name = "name";//定义name属性,为了与下面set的name属性区别开
  5. private String content = "content" ;// 定义content属性,为了与下面set的content属性区别开
  6. private boolean flag = true ;   // 设置标志位,初始时先生产
  7. private Lock lock = new ReentrantLock();
  8. private Condition condition = lock.newCondition(); //产生一个Condition对象
  9. public  void set(String name,String content){
  10. lock.lock();
  11. try{
  12. while(!flag){
  13. condition.await() ;
  14. }
  15. this.setName(name) ;    // 设置名称
  16. Thread.sleep(300) ;
  17. this.setContent(content) ;  // 设置内容
  18. flag  = false ; // 改变标志位,表示可以取走
  19. condition.signal();
  20. }catch(InterruptedException e){
  21. e.printStackTrace() ;
  22. }finally{
  23. lock.unlock();
  24. }
  25. }
  26. public void get(){
  27. lock.lock();
  28. try{
  29. while(flag){
  30. condition.await() ;
  31. }
  32. Thread.sleep(300) ;
  33. System.out.println(this.getName() +
  34. " --> " + this.getContent()) ;
  35. flag  = true ;  // 改变标志位,表示可以生产
  36. condition.signal();
  37. }catch(InterruptedException e){
  38. e.printStackTrace() ;
  39. }finally{
  40. lock.unlock();
  41. }
  42. }
  43. public void setName(String name){
  44. this.name = name ;
  45. }
  46. public void setContent(String content){
  47. this.content = content ;
  48. }
  49. public String getName(){
  50. return this.name ;
  51. }
  52. public String getContent(){
  53. return this.content ;
  54. }
  55. }
  56. class Producer implements Runnable{ // 通过Runnable实现多线程
  57. private Info info = null ;      // 保存Info引用
  58. public Producer(Info info){
  59. this.info = info ;
  60. }
  61. public void run(){
  62. boolean flag = true ;   // 定义标记位
  63. for(int i=0;i<10;i++){
  64. if(flag){
  65. this.info.set("姓名--1","内容--1") ;    // 设置名称
  66. flag = false ;
  67. }else{
  68. this.info.set("姓名--2","内容--2") ;    // 设置名称
  69. flag = true ;
  70. }
  71. }
  72. }
  73. }
  74. class Consumer implements Runnable{
  75. private Info info = null ;
  76. public Consumer(Info info){
  77. this.info = info ;
  78. }
  79. public void run(){
  80. for(int i=0;i<10;i++){
  81. this.info.get() ;
  82. }
  83. }
  84. }
  85. public class ThreadCaseDemo{
  86. public static void main(String args[]){
  87. Info info = new Info(); // 实例化Info对象
  88. Producer pro = new Producer(info) ; // 生产者
  89. Consumer con = new Consumer(info) ; // 消费者
  90. new Thread(pro).start() ;
  91. //启动了生产者线程后,再启动消费者线程
  92. try{
  93. Thread.sleep(500) ;
  94. }catch(InterruptedException e){
  95. e.printStackTrace() ;
  96. }
  97. new Thread(con).start() ;
  98. }
  99. }

执行后,同样可以得到如下的结果:

姓名--1 --> 内容--1
姓名--2 --> 内容--2
姓名--1 --> 内容--1
姓名--2 --> 内容--2
姓名--1 --> 内容--1
姓名--2 --> 内容--2
姓名--1 --> 内容--1
姓名--2 --> 内容--2
姓名--1 --> 内容--1
姓名--2 --> 内容--2
    从以上并不能看出用条件变量的await()、signal()、signalAll()方法比用Object对象的wait()、notify()、notifyAll()方法实现线程间协作有多少优点,但它在处理更复杂的多线程问题时,会有明显的优势。所以,Lock和Condition对象只有在更加困难的多线程问题中才是必须的。

读写锁

另外,synchronized获取的互斥锁不仅互斥读写操作、写写操作,还互斥读读操作,而读读操作时不会带来数据竞争的,因此对对读读操作也互斥的话,会降低性能。Java 5中提供了读写锁,它将读锁和写锁分离,使得读读操作不互斥,获取读锁和写锁的一般形式如下:

  1. ReadWriteLock rwl = new ReentrantReadWriteLock();
  2. rwl.writeLock().lock()  //获取写锁
  3. rwl.readLock().lock()  //获取读锁

   用读锁来锁定读操作,用写锁来锁定写操作,这样写操作和写操作之间会互斥,读操作和写操作之间会互斥,但读操作和读操作就不会互斥。

【Java并发编程】:并发新特性—Lock锁和条件变量的更多相关文章

  1. 转: 【Java并发编程】之二十:并发新特性—Lock锁和条件变量(含代码)

    简单使用Lock锁 Java5中引入了新的锁机制--Java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作.Lock接 ...

  2. 并发编程(八)Lock锁

    一.引言 线程并发的过程中,肯定会设计到一个变量共享的概念,那么我们在多线程运行过程中,怎么保证每个先拿获取的变量信息都是最新且有序的呢?这一篇我们来专门学习一下Lock锁. 我们先来了解几个概念: ...

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

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

  4. Java 多线程:并发编程的三大特性

    Java 多线程:并发编程的三大特性 作者:Grey 原文地址: 博客园:Java 多线程:并发编程的三大特性 CSDN:Java 多线程:并发编程的三大特性 可见性 所谓线程数据的可见性,指的就是内 ...

  5. 并发编程学习笔记(6)----公平锁和ReentrantReadWriteLock使用及原理

    (一)公平锁 1.什么是公平锁? 公平锁指的是在某个线程释放锁之后,等待的线程获取锁的策略是以请求获取锁的时间为标准的,即使先请求获取锁的线程先拿到锁. 2.在java中的实现? 在java的并发包中 ...

  6. Python 3 并发编程多进程之进程同步(锁)

    Python 3 并发编程多进程之进程同步(锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,竞争带来的结果就是错乱,如何控制,就是加锁处理. 1. ...

  7. StampedLock:一个并发编程中非常重要的票据锁

    摘要:一起来聊聊这个在高并发环境下比ReadWriteLock更快的锁--StampedLock. 本文分享自华为云社区<[高并发]一文彻底理解并发编程中非常重要的票据锁--StampedLoc ...

  8. JAVA JDK1.5-1.9新特性

    1.51.自动装箱与拆箱:2.枚举(常用来设计单例模式)3.静态导入4.可变参数5.内省 1.61.Web服务元数据2.脚本语言支持3.JTable的排序和过滤4.更简单,更强大的JAX-WS5.轻量 ...

  9. Java 8 正式发布,新特性全搜罗

    经过2年半的努力.屡次的延期和9个里程碑版本,甲骨文的Java开发团队终于发布了Java 8正式版本. Java 8版本最大的改进就是Lambda表达式,其目的是使Java更易于为多核处理器编写代码: ...

随机推荐

  1. UVaLive 2796 Concert Hall Scheduling (最小费用流)

    题意:个著名的音乐厅因为财务状况恶化快要破产,你临危受命,试图通过管理的手段来拯救它,方法之一就是优化演出安排,既聪明的决定接受或拒绝哪些乐团的演出申请,使得音乐厅的收益最大化.该音乐厅有两个完全相同 ...

  2. iPhone 物理尺寸与分辨率

    //    iPhone  物理尺寸(pt:Point)   分辨率(px) //    4S        320*480(3.5英寸)     640*960 //    5,5c,5S   32 ...

  3. SSH整合 第四篇 Spring的IoC和AOP

    这篇主要是在整合Hibernate后,测试IoC和AOP的应用. 1.工程目录(SRC) 2.IoC 1).一个Service测试类 /* * 加入spring容器 */ private Applic ...

  4. Andfix热修复原理

    一.前言 最近腾讯弄出一个Tinker热修复框架,那么本文先不介绍这个框架,先来介绍一下阿里的一个热修复框架AndFix,这个框架出来已经很长时间了,但是看网上没有太多非常详细的讲解,这里就来做一次分 ...

  5. 几个经典的数学库之一学习---VCGlib(1)

    1. VCG Libary是Visulization and Computer Graphics Libary(可视化与计算机图形学库)的缩写,是一个开源的C++模板库,用于三角网格和四面体网格的控制 ...

  6. 解决 multiple definition of

    总结了解决multiple definition of的方法: 问题原因:    当多个文件包含同一个头文件时,并且你的.H里面没有加上条件编译#ifndef TEST_H#define TEST_H ...

  7. hdu 4983 欧拉函数

    http://acm.hdu.edu.cn/showproblem.php?pid=4983 求有多少对元组满足题目中的公式. 对于K=1的情况,等价于gcd(A, N) * gcd(B, N) = ...

  8. 19、Docker Compose

      编排(Orchestration)功能是复杂系统实现灵活可操作性的关键.特别是docker应用场景中,编排意味着用户可以灵活地对各种容器资源实现定义和管理.   在我们部署多容器的应用时: 要从D ...

  9. (二)从分布式一致性谈到CAP理论、BASE理论

    问题的提出 在计算机科学领域,分布式一致性是一个相当重要且被广泛探索与论证问题,首先来看三种业务场景. 1.火车站售票 假如说我们的终端用户是一位经常坐火车的旅行家,通常他是去车站的售票处购买车票,然 ...

  10. Microsoft SQL Server 2012 管理 (1): 安装配置SQL Server 重点

    SQL Server 可以在实例,数据库,列,查询分别指定排序规则 /* Module 1 - working with Clollations */ -- 1.1 Obtain the Instan ...