文档章节

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

pan_1308
 pan_1308
发布于 2017/09/08 11:24
字数 1690
阅读 8
收藏 0
点赞 0
评论 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
博文 93
码字总数 58819
作品 0
黄冈
Java 使用 happen-before 规则实现共享变量的同步操作

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

stateIs0
01/20
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 8新特性探究(十)StampedLock将是解决同步问题的新宠

Java8就像一个宝藏,一个小的API改进,也足与写一篇文章,比如同步,一直是多线程并发编程的一个老话题,相信没有人喜欢同步的代码,这会降低应用的吞吐量等性能指标,最坏的时候会挂起死机,...

OSC闲人
2014/05/13
0
30
【Java并发专题】27篇文章详细总结Java并发基础知识

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

你听___
05/06
0
0
Java多线程学习(二)synchronized关键字(2)

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀
04/16
0
0
Java中的ReentrantLock和synchronized两种锁定机制的对比

多线程和并发性并不是什么新内容,但是 Java 语言设计中的创新之一就是,它是第一个直接把跨平台线程模型和正规的内存模型集成到语言中的主流语言。核心类库包含一个 Thread类,可以用它来构...

donghan
2013/11/28
0
0
关于Java里面多线程同步的一些知识

# 关于Java里面多线程同步的一些知识 对于任何Java开发者来说多线程和同步是一个非常重要的话题。比较好的掌握同步和线程安全相关的知识将使得我们则更加有优势,同时这些知识并不是非常容易...

欧阳海阳
07/13
0
0
ReentrantLock 与Synchronized 的区别

主要相同点:Lock能完成synchronized所实现的所有功能 主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定 要求程序员手工释放,并且必须...

刘小兵2014
2011/10/14
0
0
Java中的ReentrantLock和synchronized两种锁定机制的对比

多线程和并发性并不是什么新内容,但是 Java 语言设计中的创新之一就是,它是第一个直接把跨平台线程模型和正规的内存模型集成到语言中的主流语言。核心类库包含一个 Thread 类,可以用它来构...

zaizai_loong
2013/08/12
0
0
Java并发机制及锁的实现原理

Java并发编程概述 并发编程的目的是为了让程序运行得更快,但是,并不是启动更多的线程就能让程序最大限度地并发执行。在进行并发编程时,如果希望通过多线程执行任务让程序运行得更快,会面...

烂猪皮
05/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

流利阅读笔记29-20180718待学习

高等教育未来成谜,前景到底在哪里? Ray 2018-07-18 1.今日导读 在这个信息爆炸的年代,获取知识是一件越来越容易的事情。人们曾经认为,如此的时代进步会给高等教育带来众多便利。但事实的...

aibinxiao
7分钟前
2
0
第15章FTP服务搭建与配置

15.1FTP介绍 FTP多用于Windows传文件到linux rz sz在文件超过4G,就无法使用了——>安装包yum install -y install lrzsz rz把 window 上的文件传输到 linux 上 sz 把 linux 上的文件传输到 ...

Linux学习笔记
15分钟前
0
0
OSChina 周三乱弹 —— 你被我从 osc 老婆们名单中踢出了

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @小鱼丁:分享五月天的单曲《后来的我们 (电影《后来的我们》片名曲)》: 《后来的我们 (电影《后来的我们》片名曲)》- 五月天 手机党少年们想...

小小编辑
19分钟前
6
1
Spring Boot Admin 2.0开箱体验

概述 在我之前的 《Spring Boot应用监控实战》 一文中,讲述了如何利用 Spring Boot Admin 1.5.X 版本来可视化地监控 Spring Boot 应用。说时迟,那时快,现在 Spring Boot Admin 都更新到 ...

CodeSheep
38分钟前
0
0
Python + Selenium + Chrome 使用代理 auth 的用户名密码授权

米扑代理,全球领导的代理品牌,专注代理行业近十年,提供开放、私密、独享代理,并可免费试用 米扑代理官网:https://proxy.mimvp.com 本文示例,是结合米扑代理的私密、独享、开放代理,专...

sunboy2050
今天
0
0
实现异步有哪些方法

有哪些方法可以实现异步呢? 方式一:java 线程池 示例: @Test public final void test_ThreadPool() throws InterruptedException { ScheduledThreadPoolExecutor scheduledThre......

黄威
今天
1
0
linux服务器修改mtu值优化cpu

一、jumbo frames 相关 1、什么是jumbo frames Jumbo frames 是指比标准Ethernet Frames长的frame,即比1518/1522 bit大的frames,Jumbo frame的大小是每个设备厂商规定的,不属于IEEE标准;...

六库科技
今天
0
0
牛客网刷题

1. 二维数组中的查找(难度:易) 题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入...

大不了敲一辈子代码
今天
0
0
linux系统的任务计划、服务管理

linux任务计划cron 在linux下,有时候要在我们不在的时候执行一项命令,或启动一个脚本,可以使用任务计划cron功能。 任务计划要用crontab命令完成 选项: -u 指定某个用户,不加-u表示当前用...

黄昏残影
昨天
0
0
设计模式:单例模式

单例模式的定义是确保某个类在任何情况下都只有一个实例,并且需要提供一个全局的访问点供调用者访问该实例的一种模式。 实现以上模式基于以下必须遵守的两点: 1.构造方法私有化 2.提供一个...

人觉非常君
昨天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部