文档章节

【Java并发编程实战】– 使用锁实现同步 lock_1

pan_1308
 pan_1308
发布于 2017/09/08 11:24
字数 1690
阅读 9
收藏 0

一、概述

  Lock是 java.util.concurrent.locks 包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题。

------------------------------------------------------------------------------------------------------------

lock 产生的背景:

采用synchronized关键字来实现同步的话,就会导致一个问题:如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。另外,通过Lock可以知道线程有没有成功获取到锁。这个是 synchronized 无法办到的。

------------------------------------------------------------------------------------------------------------

sychronized 与 lock 的 比较

1、Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;

2、Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

------------------------------------------------------------------------------------------------------------

需要注意的地方:

sychronized 修饰的方法或者语句块在代码执行完之后锁 自动释放,而用 Lock 需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在 try 内,释放锁放在 finally 内。

------------------------------------------------------------------------------------------------------------

二、实现

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 打印队列类
public class PrintQueue {

	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
	//声明并实例化一个锁对象
	private final Lock queueLock = new ReentrantLock();
	
	//实现 打印方法
	public void printJob(Object doc){
		 queueLock.lock(); // 获取锁对象的控制
		 try {
			System.out.println(Thread.currentThread().getName() + " - printJob 正在打印,请等待...,时间:" + sdf.format(new Date()));
			TimeUnit.SECONDS.sleep(2);
		 } catch (InterruptedException e) {
			e.printStackTrace();
		 } finally{
			queueLock.unlock(); // 释放锁对象的控制.(一般是 finally 中手动释放)
		 }
	}
	
}
import java.text.SimpleDateFormat;
import java.util.Date;

public class Job implements Runnable{

	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
	
	private PrintQueue printQueue;
	public Job(PrintQueue printQueue){
		this.printQueue = printQueue;
	}
	
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "准备打印...,时间:" + sdf.format(new Date()));
		printQueue.printJob(new Object()); // 发送 打印任务.
		System.out.println(Thread.currentThread().getName() + "完成打印...,时间:" + sdf.format(new Date()));
	}

}
public class TestJob {

	public static void main(String[] args) {
		PrintQueue printQueue = new PrintQueue();
		for(int i=0;i<3;i++){
			Thread thread = new Thread(new Job(printQueue)); //3个线程创建3个打印工作
			thread.start();
		}
	}
	
	/** console 结果:
	 *  Thread-1准备打印...,时间:2017-09-08 10:43:06
		Thread-2准备打印...,时间:2017-09-08 10:43:06
		Thread-0准备打印...,时间:2017-09-08 10:43:06
		Thread-1 - printJob 正在打印,请等待...,时间:2017-09-08 10:43:06
		Thread-1完成打印...,时间:2017-09-08 10:43:08
		Thread-2 - printJob 正在打印,请等待...,时间:2017-09-08 10:43:08
		Thread-2完成打印...,时间:2017-09-08 10:43:10
		Thread-0 - printJob 正在打印,请等待...,时间:2017-09-08 10:43:10
		Thread-0完成打印...,时间:2017-09-08 10:43:12
	 */
}

分析

示例 主要部分是打印队列类 PrintQueue中的 printJob()方法。

使用 锁实现了一个临界区,并且保证同一个时间只有一个执行线程访问这个临界区时,必须创建 ReentrantLock 对象。在这个临界区的开始,必须通过 lock() 方法 获取对锁的控制。当线程A 访问这个方法时,如果没有其他线程获取对这个锁的控制,lock() 方法将让线程 A 获得 锁并且允许 它立即执行 临界区代码。否则,如果其他线程 B 正在执行这个锁保护的临界区代码,lock() 方法将让线程 A 休眠直到 线程 B 执行完临界区的代码。

在线程 离开临界区的时候,必须使用 unlock()方法来 释放它持有的锁,以让其他线程来访问临界区。如果在离开临界区的时候没有调用 unlock() 方法,其他线程将永久地等待,从而导致死锁情景。如果在 临界区使用了 try-catch 块,记得必须将 unlock() 方法 放入 finally部分。

三、常用方法

 1、lock()  

      如果锁已被其他线程获取,则进行等待。如果采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此,一般来说,使用Lock必须在 try…catch… 块中进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被被释放,防止死锁的发生。

Lock lock = ...;
lock.lock();
try{
    //处理任务
}catch(Exception ex){

}finally{
    lock.unlock();   //释放锁
}

2、tryLock() & tryLock(long time, TimeUnit unit)

tryLock() 方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true;如果获取失败(即锁已被其他线程获取),则返回false,也就是说,这个方法无论如何都会立即返回(在拿不到锁时不会一直在那等待)。

tryLock (long time, TimeUnit unit) 方法和 tryLock() 方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,同时可以响应中断。如果一开始拿到锁或者在等待期间内拿到了锁,则返回 true。

Lock lock = ...;
if(lock.tryLock()) {
     try{
         //处理任务
     }catch(Exception ex){

     }finally{
         lock.unlock();   //释放锁
     } 
}else {
    //如果不能获取锁,则直接做其他事情
}

四、Lock和synchronized的选择

总的来说,Lock和synchronized有以下几点不同:

1、 Lock是一个接口,是JDK层面的实现;而synchronized是Java中的关键字,是Java的内置特性,是JVM层面的实现;

2、 synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3、Lock 可以让等待锁的线程响应中断,而使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4、 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;

5、 Lock可以提高多个线程进行 读操作 的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的。而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

 

© 著作权归作者所有

共有 人打赏支持
pan_1308
粉丝 4
博文 95
码字总数 58819
作品 0
黄冈
私信 提问
【死磕Java并发】—– 死磕 Java 并发精品合集

【死磕 Java 并发】系列是 LZ 在 2017 年写的第一个死磕系列,一直没有做一个合集,这篇博客则是将整个系列做一个概览。 先来一个总览图: 【高清图,请关注“Java技术驿站”公众号,回复:脑...

chenssy
07/22
0
0
Java 使用 happen-before 规则实现共享变量的同步操作

前言 熟悉 Java 并发编程的都知道,JMM(Java 内存模型) 中的 happen-before(简称 hb)规则,该规则定义了 Java 多线程操作的有序性和可见性,防止了编译器重排序对程序结果的影响。按照官方的...

stateIs0
01/20
0
0
Java synchronized 和 ReentrantLock对比

Java synchronized 和 ReentrantLock对比 前段时间学习了java的线程同步的一些知识,认识到了线程同步的关键字synchronized 和 线程锁java.util.concurrent.lock包下的 ReentrantLock ,下面...

介鬼
06/16
0
0
读书笔记之《Java并发编程的艺术》-java中的锁

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
0
0
【Java并发专题】27篇文章详细总结Java并发基础知识

努力的意义,就是,在以后的日子里,放眼望去全是自己喜欢的人和事! github:https://github.com/CL0610/Java-concurrency,欢迎题issue和Pull request。所有的文档都是自己亲自码的,如果觉...

你听___
05/06
0
0

没有更多内容

加载失败,请刷新页面

加载更多

java.util.Concurrent.Semaphore 源码

类图 源码: package java.util.concurrent;import java.util.Collection;import java.util.concurrent.locks.AbstractQueuedSynchronizer;public class Semaphore implements ja......

狼王黄师傅
19分钟前
2
0
Kubernetes里的secret最基本的用法

Secret解决了密码、token、密钥等敏感数据的配置问题,使用Secret可以避免把这些敏感数据以明文的形式暴露到镜像或者Pod Spec中。 Secret可以以Volume或者环境变量的方式使用。 使用如下命令...

JerryWang_SAP
昨天
2
0
2018-11-20学习笔记

1. python数据类型: 给变量赋值什么样的值,变量就是什么样的类型 给变量赋值整数,变量就是整数类型 给变量赋值字符串,变量就是字符串类型 123 和“123”一样吗? 在python中 单引号 与双...

laoba
昨天
1
0
使用 React 和 Vue 创建相同的应用,他们有什么差异?

在工作中应用 Vue 之后,我对它有了相当深刻的理解。 不过,俗话说「外国的月亮比较圆」,我好奇「外国的」 React 是怎么样的。 我阅读了 React 文档并观看了一些教程视频,虽然它们很棒,但...

阿K1225
昨天
4
0
2天闭门培训|以太坊智能合约从入门到实战(北京)

2天培训 16个课时 探寻技术原理,精通以太坊智能合约开发 以太坊智能合约是现在应用的最广泛的区块链应用开发方式,HiBlock区块链社区针对以太坊智能合约的学习特别推出2天闭门研修班,通过2...

HiBlock
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部