【Java并发编程实战】– 使用锁实现同步 lock_1
博客专区 > pan_1308 的博客 > 博客详情
【Java并发编程实战】– 使用锁实现同步 lock_1
pan_1308 发表于3个月前
【Java并发编程实战】– 使用锁实现同步 lock_1
  • 发表于 3个月前
  • 阅读 4
  • 收藏 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。所以说,在具体使用时要根据适当情况选择。

 

共有 人打赏支持
粉丝 5
博文 77
码字总数 56678
×
pan_1308
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: