声明:本文是《 Java 7 Concurrency Cookbook 》的第二章,作者: Javier Fernández González     译者:许巧辉 校对:方腾飞

使用Lock同步代码块

Java提供另外的机制用来同步代码块。它比synchronized关键字更加强大、灵活。它是基于Lock接口和实现它的类(如ReentrantLock)。这种机制有如下优势:

  • 它允许以一种更灵活的方式来构建synchronized块。使用synchronized关键字,你必须以结构化方式得到释放synchronized代码块的控制权。Lock接口允许你获得更复杂的结构来实现你的临界区。
  • Lock 接口比synchronized关键字提供更多额外的功能。新功能之一是实现的tryLock()方法。这种方法试图获取锁的控制权并且如果它不能获取该锁,是因为其他线程在使用这个锁,它将返回这个锁。使用synchronized关键字,当线程A试图执行synchronized代码块,如果线程B正在执行它,那么线程A将阻塞直到线程B执行完synchronized代码块。使用锁,你可以执行tryLock()方法,这个方法返回一个 Boolean值表示,是否有其他线程正在运行这个锁所保护的代码。
  • 当有多个读者和一个写者时,Lock接口允许读写操作分离。
  • Lock接口比synchronized关键字提供更好的性能。

在这个指南中,你将学习如何通过锁来同步代码块和通过Lock接口及其实现者ReentrantLock类来创建临界区,实现一个程序来模拟打印队列。

准备工作

这个指南的例子使用Eclipse IDE实现。如果你使用Eclipse或其他IDE,如NetBeans,打开它并创建一个新的Java项目。

如何做…

按以下步骤来实现的这个例子:

1.创建PrintQueue类,来实现打印队列。

1 public class PrintQueue {

2.声明一个Lock对象,并且使用ReentrantLock类的一个新对象来初始化它。

1 private final Lock queueLock=new ReentrantLock();

3.实现printJob()方法,它将接收Object对象作为参数,并且不会返回任何值。

1 public void printJob(Object document){

4.在printJob()方法内部,通过调用lock()方法来获取Lock对象的控制权。

1 queueLock.lock();

5.然后,包含以下代码来模拟文档的打印:

1 try {
2 Long duration=(long)(Math.random()*10000);
3 System.out.println(Thread.currentThread().getName()+ ":
4 PrintQueue: Printing a Job during "+(duration/1000)+
5 " seconds");
6 Thread.sleep(duration);
7 catch (InterruptedException e) {
8 e.printStackTrace();
9 }

6.最后,通过调用unlock()方法来释放Lock对象的控制。

1 finally {
2 queueLock.unlock();
3 }

7.创建一个Job类,并指定它实现Runnable接口。

1 public class Job implements Runnable {

8.声明一个PrintQueue类的对象,并通过实现类(Job类)的构造器来初始化这个对象。

1 private PrintQueue printQueue;
2 public Job(PrintQueue printQueue){
3 this.printQueue=printQueue;
4 }

9.实现run()方法,它使用PrintQueue对象来发送一个打印任务。

1 @Override
2 public void run() {
3 System.out.printf("%s: Going to print a document\n", Thread.
4 currentThread().getName());
5 printQueue.printJob(new Object());
6 System.out.printf("%s: The document has been printed\n",
7 Thread.currentThread().getName());
8 }

10.通过创建类名为Main,且包括main()方法来实现这个示例的主类。

1 public class Main {
2 public static void main(String[] args) {

11.创建一个共享的PrintQueue对象。

1 PrintQueue printQueue=new PrintQueue();

12.创建10个Job对象,并且使用10个线程来运行它们。

1 Thread thread[]=new Thread[10];
2 for (int i=0; i<10; i++){
3 thread[i]=new Thread(new Job(printQueue),"Thread "+ i);
4 }

13.启动这10个线程。

1 for (int i=0; i<10; i++){
2 thread[i].start();
3 }

它是如何工作的…

从以下截图,你可以看到执行这个示例一部分的输出:

在 printJob()中,PrintQueue类是这个示例的关键所在。当我们通过锁来实现一个临界区并且保证只有一个执行线程能运行一个代码块,我们必 须创建一个ReentrantLock对象。在临界区的起始部分,我们必须通过使用lock()方法来获得锁的控制权。当一个线程A调用这个方法时,如果 没有其他线程持有这个锁的控制权,那么这个方法就会给线程A分配这个锁的控制权并且立即返回允许线程A执行这个临界区。否则,如果其他线程B正在执行由这 个锁控制的临界区,lock()方法将会使线程A睡眠直到线程B完成这个临界区的执行。

在临界区的尾部,我们必须使用unlock()方法来释放锁的控制权,允许其他线程运行这个临界区。如果你在临界区的尾部没有调用unlock()方法,那么其他正在等待该代码块的线程将会永远等待,造成 死锁情况。如果你在临界区使用try-catch代码块,别忘了在finally部分的内部包含unlock()方法的代码。

不止这些…

Lock 接口(和ReentrantLock类)包含其他方法来获取锁的控制权,那就是tryLock()方法。这个方法与lock()方法的最大区别是,如果一 个线程调用这个方法不能获取Lock接口的控制权时,将会立即返回并且不会使这个线程进入睡眠。这个方法返回一个boolean值,true表示这个线程 获取了锁的控制权,false则表示没有。

注释:考虑到这个方法的结果,并采取相应的措施,这是程序员的责任。如果这个方法返回false值,预计你的程序不会执行这个临界区。如果是这样,你可能会在你的应用程序中得到错误的结果。

ReentrantLock类也允许递归调用(锁的可重入性,译者注),当一个线程有锁的控制权并且使用递归调用,它延续了锁的控制权,所以调用lock()方法将会立即返回并且继续递归调用的执行。此外,我们也可以调用其他方法。

更多信息

你必须要非常小心使用锁来避免死锁,这种情况发生在,当两个或两个以上的线程被阻塞等待将永远不会解开的锁。比如,线程A锁定Lock(X)而线程B锁定 Lock(Y)。如果现在,线程A试图锁住Lock(Y)而线程B同时也试图锁住Lock(X),这两个线程将无限期地被阻塞,因为它们等待的锁将不会被解开。请注意,这个问题的发生是因为这两个线程尝试以相反的顺序获取锁(译者注:锁顺序死锁)。在附录中,提供了一些很好的并发编程设计的建议,适当的设计并发应用程序,来避免这些死锁问题。

参见

  • 在第2章,基本线程同步中的同步方法指南
  • 在第2章,基本线程同步中的在锁中使用多条件的指南
  • 在第8章,测试并发应用程序中的监控Lock接口的指南
  • 文章转自 并发编程网-ifeve.com

基本线程同步(五)使用Lock同步代码块相关推荐

  1. Syschronized同步锁和lock同步锁的区别

    Syschronized和lock的区别 1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁), synchronized是隐式锁,出了作用域自动释放 2,Lock只有代码块锁,synchroniz ...

  2. 单例设计模式八种方式——5) 懒汉式(线程安全,同步代码块) 6) 双重检查 7) 静态内部类 8) 枚举

    懒汉式(线程安全,同步代码块)应用实例 优缺点说明: 1) 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低, 改为同步产生实例化的的代码块 2) 但是这种同步并不能起到线程同步的作 ...

  3. 020.day20 线程概述 多线程优缺点 线程的创建 线程常用方法 生命周期 多线程同步...

    目录 多线程 一.线程概述 四.线程常用方法 多线程 一.线程概述 1. 进程 正在执行的应用程序(java.exe),一个可执行的程序一次运行的过程 独立性:不同进程之间相互独立 动态性:是一直活动 ...

  4. 从不同步的代码块中调用了对象同步方法。_Java中Synchronized的用法

    Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行.拉勾IT课小编为大家分析. synchronized是Ja ...

  5. C#线程系列讲座(4):同步与死锁

    本文为原创,如需转载,请注明作者和出处,谢谢! 上一篇:C#线程系列讲座(3):线程池和文件下载服务器   虽然线程可以在一定程度上提高程序运行的效率,但也会产生一些副作用.让我们先看看如下的代码: ...

  6. java同步方法同步块_java 同步代码块与同步方法

    同步代码块 synchronized(obj) {//代码块 } obj 为同步监视器,以上代码的含义为:线程开始执行同步代码块(中的代码)之前,必须先获得对同步监视器的锁定. 代码块中的代码是执行代 ...

  7. Java多线程:线程间通信之Lock

    Java 5 之后,Java在内置关键字sychronized的基础上又增加了一个新的处理锁的方式,Lock类. 由于在Java线程间通信:volatile与sychronized中,我们已经详细的了 ...

  8. 【第一阶段 day23 面向对象】构造方法 构造代码块与局部代码块 this

    1.构造方法 1.格式 没有返回值类型,且方法名与本类类名一致的方法 2.作用 用来创建对象,每次触发,都可以创建一个对应的对象 3.注意事项 (1)每一个类默认一个无参构造 (2)一旦提供了其他的构 ...

  9. Java20-day11【实现多线程(进程、线程-调度-控制-生命周期)、线程同步(同步代码块、线程安全、Lock)、生产者消费者(模式概述、案例)】

    视频+资料[链接:https://pan.baidu.com/s/1MdFNUADVSFf-lVw3SJRvtg   提取码:zjxs] Java基础--学习笔记(零起点打开java世界的大门)--博 ...

最新文章

  1. windows API(一)
  2. 让你的英语口语妙语连珠的句子
  3. python 生成pdf收据_python如何与以太坊交互并将区块链信息写入SQLite
  4. 腾讯云数据库闪耀曼哈顿,CynosDB回馈开源社区
  5. 用Visual Studio Code调试nodejs
  6. 第六十七期:全球500强公司的2100万登录信息惊现于暗网上!
  7. adb 抓取日志到指定文件,文件命名为当前电脑时间
  8. 美将遣返庇护申请者至墨西哥 弱势人士有望豁免
  9. 去德国做汉语老师,月薪四万
  10. 训练作用_感觉统合是什么意思,感觉统合训练有什么作用
  11. 【持续更新】2000-2022年英伟达历代桌面Quadro显卡列表,Quadro显卡发布日期
  12. Android Framework 音频子系统(02)音频系统框架
  13. 打不开网页服务器丢失,苹果浏览器safari打不开,丢失网络
  14. mysql 删除临时表的语句_MySQL如何创建和删除临时表_MySQL
  15. keep-alive的用法
  16. 网络攻击更难预料,IoT到底是福是祸?
  17. html onclick事件无效 巨坑
  18. blender-GIS安装教程
  19. 图片征集网站源码_征集提名:2013年卡尔加里最烂的网站
  20. 《STM32单片机开发应用教程(HAL库版)---基于国信长天嵌入式竞赛实训平台(CT117E-M4)》第二章 软件安装与使用

热门文章

  1. C++的decltype
  2. SPOJ1716 GSS3(线段树)
  3. Hello TensorFlow
  4. HBase的BulkLoad详解
  5. Java 身份证号码识别系统
  6. zookeeper和Kafka的关系
  7. 设置mysql8的root可以远程访问
  8. 详解fianl,finally,finalize关键字
  9. c语言有趣的软件,一个有趣的小程序
  10. apache 支持php urlmanager,Yii中urlManager的配置