锁的概念

从jdk发行1.5版本之后,在原来synchronize的基础上,增加了重入锁ReentrantLock。

本文就不介绍synchronize了,有兴趣的同学可以去了解一下,本文重点介绍ReentrantLock。

锁是什么?

并发编程的时候,比如说有一个业务是读写操作,那多个线程执行这个业务就会造成已经写入的数据又写一遍,就会造成数据错乱。

所以需要引入锁,进行数据同步,强制使得该业务执行的时候只有一个线程在执行,从而保证不会插入多条重复数据。

一些共享资源也是需要加锁,从而保证数据的一致性。

关于锁的概念,也就不过多篇幅介绍,有很多概念性的东西,需要自己取找本书狠狠啃一啃,本文主要是给大家介绍如何使用锁。

使用ReentrantLock同步

首先来看第一个实例:用两个线程来在控制台有序打出1,2,3。

  1. public class FirstReentrantLock {
  2.  
  3. public static void main(String[] args) {
  4. Runnable runnable = new ReentrantLockThread();
  5. new Thread(runnable, "a").start();
  6. new Thread(runnable, "b").start();
  7. }
  8.  
  9. }
  10.  
  11. class ReentrantLockThread implements Runnable {
  12.  
  13. @Override
  14. public void run() {
  15. for (int i = 0; i < 3; i++) {
  16. System.out.println(Thread.currentThread().getName() + "输出了: " + i);
  17. }
  18. }
  19.  
  20. }

执行FirstReentrantLock ,查看控制台输出:

 

可以看到,并没有顺序,杂乱无章。

那使用ReentrantLock加入锁,代码如下:

  1. package com.chapter2;
  2.  
  3. import java.util.concurrent.locks.ReentrantLock;
  4.  
  5. /**
  6. * @author tangj
  7. *
  8. * 如何使用ReentrantLock
  9. */
  10. public class FirstReentrantLock {
  11.  
  12. public static void main(String[] args) {
  13. Runnable runnable = new ReentrantLockThread();
  14. new Thread(runnable, "a").start();
  15. new Thread(runnable, "b").start();
  16. }
  17.  
  18. }
  19.  
  20. class ReentrantLockThread implements Runnable {
  21. // 创建一个ReentrantLock对象
  22. ReentrantLock lock = new ReentrantLock();
  23.  
  24. @Override
  25. public void run() {
  26. try {
  27. // 使用lock()方法加锁
  28. lock.lock();
  29. for (int i = 0; i < 3; i++) {
  30. System.out.println(Thread.currentThread().getName() + "输出了: " + i);
  31. }
  32. } finally {
  33. // 别忘了执行unlock()方法释放锁
  34. lock.unlock();
  35. }
  36.  
  37. }
  38.  
  39. }

执行FirstReentrantLock ,查看控制台输出:

有顺序的打印出了0,1,2,0,1,2.

这就是锁的作用,它是互斥的,当一个线程持有锁的时候,其他线程只能等待,待该线程执行结束,再通过竞争得到锁。

使用Condition实现线程等待和唤醒

通常在开发并发程序的时候,会碰到需要停止正在执行业务A,来执行另一个业务B,当业务B执行完成后业务A继续执行。ReentrantLock通过Condtion等待/唤醒这样的机制.

相比较synchronize的wait()和notify()/notifAll()的机制而言,Condition具有更高的灵活性,这个很关键。Conditon可以实现多路通知和选择性通知。

当使用notify()/notifAll()时,JVM时随机通知线程的,具有很大的不可控性,所以建议使用Condition。

Condition使用起来也非常方便,只需要注册到ReentrantLock下面即可。

参考下图:

接下来,使用Condition来实现等待/唤醒,并且能够唤醒制定线程

先写业务代码:

  1. package com.chapter2.howtocondition;
  2.  
  3. import java.util.concurrent.locks.Condition;
  4. import java.util.concurrent.locks.ReentrantLock;
  5.  
  6. public class MyService {
  7.  
  8. // 实例化一个ReentrantLock对象
  9. private ReentrantLock lock = new ReentrantLock();
  10. // 为线程A注册一个Condition
  11. public Condition conditionA = lock.newCondition();
  12. // 为线程B注册一个Condition
  13. public Condition conditionB = lock.newCondition();
  14.  
  15. public void awaitA() {
  16. try {
  17. lock.lock();
  18. System.out.println(Thread.currentThread().getName() + "进入了awaitA方法");
  19. long timeBefore = System.currentTimeMillis();
  20. // 执行conditionA等待
  21. conditionA.await();
  22. long timeAfter = System.currentTimeMillis();
  23. System.out.println(Thread.currentThread().getName()+"被唤醒");
  24. System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore)/1000+"s");
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. } finally {
  28. lock.unlock();
  29. }
  30. }
  31.  
  32. public void awaitB() {
  33. try {
  34. lock.lock();
  35. System.out.println(Thread.currentThread().getName() + "进入了awaitB方法");
  36. long timeBefore = System.currentTimeMillis();
  37. // 执行conditionB等待
  38. conditionB.await();
  39. long timeAfter = System.currentTimeMillis();
  40. System.out.println(Thread.currentThread().getName()+"被唤醒");
  41. System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore)/1000+"s");
  42. } catch (InterruptedException e) {
  43. e.printStackTrace();
  44. } finally {
  45. lock.unlock();
  46. }
  47. }
  48.  
  49. public void signallA() {
  50. try {
  51. lock.lock();
  52. System.out.println("启动唤醒程序");
  53. // 唤醒所有注册conditionA的线程
  54. conditionA.signalAll();
  55. } finally {
  56. lock.unlock();
  57. }
  58. }
  59.  
  60. public void signallB() {
  61. try {
  62. lock.lock();
  63. System.out.println("启动唤醒程序");
  64. // 唤醒所有注册conditionA的线程
  65. conditionB.signalAll();
  66. } finally {
  67. lock.unlock();
  68. }
  69. }
  70. }

分别实例化了两个Condition对象,都是使用同一个lock注册。注意conditionA对象的等待和唤醒只对使用了conditionA的线程有用,同理conditionB对象的等待和唤醒只对使用了conditionB的线程有用。

继续写两个线程的代码:

  1. package com.chapter2.howtocondition;
  2.  
  3. public class MyServiceThread1 implements Runnable {
  4.  
  5. private MyService service;
  6.  
  7. public MyServiceThread1(MyService service) {
  8. this.service = service;
  9. }
  10.  
  11. @Override
  12. public void run() {
  13. service.awaitA();
  14. }
  15.  
  16. }

注意:MyServiceThread1 使用了awaitA()方法,持有的是conditionA!

  1. package com.chapter2.howtocondition;
  2.  
  3. public class MyServiceThread2 implements Runnable {
  4.  
  5. private MyService service;
  6.  
  7. public MyServiceThread2(MyService service) {
  8. this.service = service;
  9. }
  10.  
  11. @Override
  12. public void run() {
  13. service.awaitB();
  14. }
  15.  
  16. }

注意:MyServiceThread2 使用了awaitB()方法,持有的是conditionB!

最后看启动类:

  1. package com.chapter2.howtocondition;
  2.  
  3. public class ApplicationCondition {
  4.  
  5. public static void main(String[] args) throws InterruptedException {
  6. MyService service = new MyService();
  7. Runnable runnable1 = new MyServiceThread1(service);
  8. Runnable runnable2 = new MyServiceThread2(service);
  9.  
  10. new Thread(runnable1, "a").start();
  11. new Thread(runnable2, "b").start();
  12.  
  13. // 线程sleep2秒钟
  14. Thread.sleep(2000);
  15. // 唤醒所有持有conditionA的线程
  16. service.signallA();
  17.  
  18. Thread.sleep(2000);
  19. // 唤醒所有持有conditionB的线程
  20. service.signallB();
  21. }
  22.  
  23. }

执行ApplicationCondition ,来看控制台输出结果:

a和b都进入各自的await()方法。首先执行的是

使用conditionA的线程被唤醒,而后再唤醒使用conditionB的线程。

学会使用Condition,那来用它实现生产者消费者模式

生产者和消费者

首先来看业务类的实现:

  1. package com.chapter2.consumeone;
  2.  
  3. import java.util.concurrent.locks.Condition;
  4. import java.util.concurrent.locks.Lock;
  5. import java.util.concurrent.locks.ReentrantLock;
  6.  
  7. public class Service {
  8.  
  9. private Lock lock = new ReentrantLock();
  10. private boolean flag = false;
  11. private Condition condition = lock.newCondition();
  12. // 以此为衡量标志
  13. private int number = 1;
  14.  
  15. /**
  16. * 生产者生产
  17. */
  18. public void produce() {
  19. try {
  20. lock.lock();
  21. while (flag == true) {
  22. condition.await();
  23. }
  24. System.out.println(Thread.currentThread().getName() + "-----生产-----");
  25. number++;
  26. System.out.println("number: " + number);
  27. System.out.println();
  28. flag = true;
  29. // 提醒消费者消费
  30. condition.signalAll();
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. } finally {
  34. lock.unlock();
  35. }
  36. }
  37.  
  38. /**
  39. * 消费者消费生产的物品
  40. */
  41. public void consume() {
  42. try {
  43. lock.lock();
  44. while (flag == false) {
  45. condition.await();
  46. }
  47. System.out.println(Thread.currentThread().getName() + "-----消费-----");
  48. number--;
  49. System.out.println("number: " + number);
  50. System.out.println();
  51. flag = false;
  52. // 提醒生产者生产
  53. condition.signalAll();
  54. } catch (InterruptedException e) {
  55. e.printStackTrace();
  56. } finally {
  57. lock.unlock();
  58. }
  59. }
  60. }

生产者线程代码:

  1. package com.chapter2.consumeone;
  2.  
  3. /**
  4. * 生产者线程
  5. *
  6. * @author tangj
  7. *
  8. */
  9. public class MyThreadProduce implements Runnable {
  10.  
  11. private Service service;
  12.  
  13. public MyThreadProduce(Service service) {
  14. this.service = service;
  15. }
  16.  
  17. @Override
  18. public void run() {
  19. for (;;) {
  20. service.produce();
  21. }
  22. }
  23.  
  24. }

消费者线程代码:

  1. package com.chapter2.consumeone;
  2.  
  3. /**
  4. * 消费者线程
  5. *
  6. * @author tangj
  7. *
  8. */
  9. public class MyThreadConsume implements Runnable {
  10.  
  11. private Service service;
  12.  
  13. public MyThreadConsume(Service service) {
  14. super();
  15. this.service = service;
  16. }
  17.  
  18. @Override
  19. public void run() {
  20. for (;;) {
  21. service.consume();
  22. }
  23. }
  24.  
  25. }

启动类:

  1. package com.chapter2.consumeone;
  2.  
  3. public class Application {
  4.  
  5. public static void main(String[] args) {
  6. Service service = new Service();
  7. Runnable produce = new MyThreadProduce(service);
  8. Runnable consume = new MyThreadConsume(service);
  9. new Thread(produce, "生产者 ").start();
  10. new Thread(consume, "消费者 ").start();
  11. }
  12.  
  13. }

执行Application,看控制台的输出:

因为采用了无限循环,生产者线程和消费者线程会一直处于工作状态,可以看到,生产者线程执行完毕后,消费者线程就会执行,以这样的交替顺序,

而且的number也遵循者生产者生产+1,消费者消费-1的一个状态。这个就是使用ReentrantLock和Condition来实现的生产者消费者模式。

顺序执行线程

充分发掘Condition的灵活性,可以用它来实现顺序执行线程。

来看业务类代码:

  1. package com.chapter2.sequencethread;
  2.  
  3. import java.util.concurrent.locks.Condition;
  4. import java.util.concurrent.locks.ReentrantLock;
  5.  
  6. public class Service {
  7.  
  8. // 通过nextThread控制下一个执行的线程
  9. private static int nextThread = 1;
  10. private ReentrantLock lock = new ReentrantLock();
  11. // 有三个线程,所有注册三个Condition
  12. Condition conditionA = lock.newCondition();
  13. Condition conditionB = lock.newCondition();
  14. Condition conditionC = lock.newCondition();
  15.  
  16. public void excuteA() {
  17. try {
  18. lock.lock();
  19. while (nextThread != 1) {
  20. conditionA.await();
  21. }
  22. System.out.println(Thread.currentThread().getName() + " 工作");
  23. nextThread = 2;
  24. conditionB.signalAll();
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. } finally {
  28. lock.unlock();
  29. }
  30. }
  31.  
  32. public void excuteB() {
  33. try {
  34. lock.lock();
  35. while (nextThread != 2) {
  36. conditionB.await();
  37. }
  38. System.out.println(Thread.currentThread().getName() + " 工作");
  39. nextThread = 3;
  40. conditionC.signalAll();
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. } finally {
  44. lock.unlock();
  45. }
  46. }
  47.  
  48. public void excuteC() {
  49. try {
  50. lock.lock();
  51. while (nextThread != 3) {
  52. conditionC.await();
  53. }
  54. System.out.println(Thread.currentThread().getName() + " 工作");
  55. nextThread = 1;
  56. conditionA.signalAll();
  57. } catch (InterruptedException e) {
  58. e.printStackTrace();
  59. } finally {
  60. lock.unlock();
  61. }
  62. }
  63. }

这里可以看到,注册了三个Condition,分别用于三个线程的等待和通知。

启动类代码:

  1. package com.chapter2.sequencethread;
  2.  
  3. /**
  4. * 线程按顺序执行
  5. *
  6. * @author tangj
  7. *
  8. */
  9. public class Application {
  10.  
  11. private static Runnable getThreadA(final Service service) {
  12. return new Runnable() {
  13. @Override
  14. public void run() {
  15. for (;;) {
  16. service.excuteA();
  17. }
  18. }
  19. };
  20. }
  21.  
  22. private static Runnable getThreadB(final Service service) {
  23. return new Runnable() {
  24. @Override
  25. public void run() {
  26. for (;;) {
  27. service.excuteB();
  28. }
  29. }
  30. };
  31. }
  32.  
  33. private static Runnable getThreadC(final Service service) {
  34. return new Runnable() {
  35. @Override
  36. public void run() {
  37. for (;;) {
  38. service.excuteC();
  39. }
  40. }
  41. };
  42. }
  43.  
  44. public static void main(String[] args) {
  45. Service service = new Service();
  46. Runnable A = getThreadA(service);
  47. Runnable B = getThreadB(service);
  48. Runnable C = getThreadC(service);
  49.  
  50. new Thread(B, "B").start();
  51. new Thread(A, "A").start();
  52. new Thread(C, "C").start();
  53. }
  54.  
  55. }

运行启动类,查看控制台输出结果:

A,B,C三个线程一直按照顺序执行。

总结

学会使用锁是学好多线程的基础,ReentrantLock相比较关键字synchronize而言,更加而且可控,所以还是推荐大家使用ReentrantLock。

最后说两句:

本文所以代码都更新到我的github中,大家可以去clone或者Fork,我会持续更新的。

点击这里进入我的Github
喜欢的朋友可以点击下方的推荐,或者写个评论我们共同探讨Java高并发!!!

Java多线程高并发学习笔记(二)——深入理解ReentrantLock与Condition的更多相关文章

  1. Java多线程高并发学习笔记(三)——深入理解线程池

    线程池最核心的一个类:ThreadPoolExecutor. 看一下该类的构造器: public ThreadPoolExecutor(int paramInt1, int paramInt2, lo ...

  2. Java多线程高并发学习笔记(一)——Thread&Runnable

    进程与线程 首先来看百度百科关于进程的介绍: 进程是一个具有独立功能的程序关于某个数据集合的一次运行活动.它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体.它不只是程序的代码,还包括当前的 ...

  3. Java多线程高并发学习笔记——阻塞队列

    在探讨可重入锁之后,接下来学习阻塞队列,这边篇文章也是断断续续的写了很久,因为最近开始学ssm框架,准备做一个自己的小网站,后续可能更新自己写网站的技术分享. 请尊重作者劳动成果,转载请标明原文链接: ...

  4. JAVA多线程高并发学习笔记(三)——Callable、Future和FutureTask

    为什么要是用Callable和Future Runnable的局限性 Executor采用Runnable作为基本的表达形式,虽然Runnable的run方法能够写入日志,写入文件,写入数据库等操作, ...

  5. Java 多线程高并发编程 笔记(一)

    本篇文章主要是总结Java多线程/高并发编程的知识点,由浅入深,仅作自己的学习笔记,部分侵删. 一 . 基础知识点 1. 进程于线程的概念 2.线程创建的两种方式 注:public void run( ...

  6. JAVA 多线程和并发学习笔记(三)

    Java并发编程中使用Executors类创建和管理线程的用法 1.类 Executors Executors类可以看做一个“工具类”.援引JDK1.6 API中的介绍: 此包中所定义的 Execut ...

  7. JAVA 多线程和并发学习笔记(二)

    一.Java中创建线程方法 1. 继承Thread类创建线程类 定义Thread类的子类,重写该类的run()方法.该方法为线程执行体. 创建Thread子类的实例.即线程对象. 调用线程对象的sta ...

  8. Java 多线程高并发编程 笔记(二)

    1. 单例模式(在内存之中永远只有一个对象) 1.1 多线程安全单例模式——不使用同步锁 public class Singleton { private static Singleton sin=n ...

  9. JAVA 多线程和并发学习笔记(四)

    1. 多进程 实现并发最直接的方式是在操作系统级别使用进程,进程是运行在它自己的地址空间内的自包容的程序.多任务操作系统可以通过周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个进程. 尽 ...

随机推荐

  1. 新入门的小白,整理一下特别简单实用的div+css兼容性的问题。

    最近整理了一下特别简单的div+css的不同浏览器的兼容性的问题,跟大家分享一下,只适合刚入门的新手,欢迎大牛们给提出意见. 1. 默认的内外边距不同 问题: 各个浏览器默认的内外边距不同 解决: * ...

  2. Wincc flexable的按钮组态

    1.题目 2.画面切换按钮组态 1)新建变量 2)组态画面进行命名 3)组态按钮常规 4)组态按钮属性 5)在事件中选择单击,系统函数中选择画面中的ActivateScreen函数切换画面 3.给变量 ...

  3. Session的引入以及Cookie的不足

    一.为什么引入session > Cookie实际上就是一个头. > 服务器会创建Cookie,并且将Cookie以一个响应头的形式发送给浏览器        > 浏览器收到Cook ...

  4. shell解析命令行的过程以及eval命令

    本文说明的是一条linux命令在执行时大致要经过哪些过程?以及这些过程的大致顺序. 1.1 shell解析命令行 shell读取和执行命令时的大致操作过程如下图: 以执行以下命令为例: echo -e ...

  5. 使用oschina的git服务器图文流程 (转)

    参考了豆沙包的教程我自己也做了一遍也来写写我的心得和体会 由于报名参加了游戏蛮牛<刀塔传奇>开源项目,服务器+客户端,所以觉着不管时间怎么着,还是或多或少做点贡献吧.毕竟这种体验应该还是第 ...

  6. 内存数据库之Apache Ingite

    上一篇文章,我们做了内存数据库的技术选型: 内存数据库技术选型 本文中,我们继续深入研究Apache Ignite,同时分享一些我们.Net的编码实践. 首先,Apache Ignite是一个内存数据 ...

  7. Tomcat8安装及配置教程

    Apache  Tomcat8.0安装及配置教程.. Apache  Tomcat8.0官方网站链接:http://tomcat.apache.org/ apache-tomcat-8.0.39-wi ...

  8. 【踩坑】angularJS 1.X版本中 ng-bind 指令多空格展示

    做项目的时候遇到的问题 1.问题描述 用户在表单某个值输入多个空格,例如:A     B,保存至服务器 在列表查询页面中使用bg-bind的指令单向绑定,结果展示位A B,连续的空格被替换为单个空格 ...

  9. 【Weblogic】在linux创建domain过慢的解决方法

    修改Linux上Weblogic使用的jdk $JAVA_HOME/jre/lib/security/java.security 文件 将 securerandom.source=file:/dev/ ...

  10. 如何在Win7安装U盘中加入USB3.0驱动的支持

    U盘安装系统出现鼠标键盘不能使用,在intel六代处理器平台,安装过程中会出现安装原生镜像不能识别或者鼠标键盘不能使用等情况,可以参考以下方法进行. 风险提示:重装或升级系统会导致系统盘数据丢失,建议 ...