Java深入学习11:Lock锁详解
一、Lock锁是什么
java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的)
Lock 接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。
Lock 实现提供了比 synchronized 关键字 更广泛的锁操作,它能以更优雅的方式处理线程同步问题。也就是说,Lock提供了比synchronized更多的功能。
二、代码分析
1-Lock类方法说明
public interface Lock { //获取锁方式1:最常用的方式。如果当前锁不可用,当前线程无法调度并进入休眠状态直到获取到锁 void lock(); //获取锁方式2:获取锁(除非当前线程被中断)。如果当前锁不可用,当前线程无法调度并进入休眠状态直到当前线程获取到锁或者其它线程中断了当前的线程。注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。 void lockInterruptibly() throws InterruptedException; //获取锁方式3:获取锁(仅当锁在调用时处于空闲状态时才获取锁)。如果成功获取锁,返回true,否则,返回false;无论如何都会立即返回。在拿不到锁时不会一直在那等待。 boolean tryLock(); //获取锁方式4:获取锁(在规定的等待时间内且线程没有被中断,如果锁处于空闲状态时则获取锁)。如果成功获取锁,返回true,否则,返回false; //如果当前锁不可用,当前线程无法调度并进入休眠状态直到(1)当前线程获取到锁(2)当前线程被其它线程中中断(3)等待时间结束 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //释放锁 void unlock(); //返回绑定到此 Lock 实例的新 Condition 实例 Condition newCondition(); }
2-Lock锁四种方法的使用方法(ReentrantLock是 Lock 的实现类)
2-1- lock()
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockTest { public static void main(String[] args) { LockDemo lockDemo = new LockDemo(); new Thread(lockDemo,"thread 1").start(); new Thread(lockDemo,"thread 2").start(); new Thread(lockDemo,"thread 3").start(); } } class LockDemo implements Runnable{ Lock lock = new ReentrantLock(); @Override public void run() { //上锁 lock.lock(); try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+" get the lock"); } catch (Exception e) { e.printStackTrace(); } finally { //释放锁 System.out.println(Thread.currentThread().getName()+" realse the lock"); lock.unlock(); } } } -----------------------------日志输出---------------------------- thread 1 get the lock thread 1 realse the lock thread 2 get the lock thread 2 realse the lock thread 3 get the lock thread 3 realse the lock
2-2-tryLock()
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TryLockTest { public static void main(String[] args) { TryLockDemo lockDemo = new TryLockDemo(); new Thread(lockDemo,"thread 1").start(); new Thread(lockDemo,"thread 2").start(); new Thread(lockDemo,"thread 3").start(); } } class TryLockDemo implements Runnable{ Lock lock = new ReentrantLock(); @Override public void run() { //上锁 if (lock.tryLock()) { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+" get lock success"); } catch (Exception e) { e.printStackTrace(); } finally { //释放锁 System.out.println(Thread.currentThread().getName()+" realse the lock"); lock.unlock(); } }else{ System.out.println(Thread.currentThread().getName() +" get lock fail"); } } } -------------------------------日志-------------------------------------- thread 2 get lock fail thread 3 get lock fail thread 1 get lock success thread 1 realse the lock
2-3- tryLock(long time, TimeUnit unit)
import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TryLockParamTest { public static void main(String[] args) { TryLockParamDemo lockDemo = new TryLockParamDemo(); new Thread(lockDemo,"thread 1").start(); new Thread(lockDemo,"thread 2").start(); new Thread(lockDemo,"thread 3").start(); } } class TryLockParamDemo implements Runnable{ Lock lock = new ReentrantLock(); @Override public void run() { try { //上锁 if (lock.tryLock(3L,TimeUnit.SECONDS)) { try { Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+" get lock success"); } finally { //释放锁 System.out.println(Thread.currentThread().getName()+" realse the lock"); lock.unlock(); } }else{ System.out.println(Thread.currentThread().getName() +" get lock fail"); } } catch (InterruptedException e) { e.printStackTrace(); } } } --------------------------------日志-------------------------------------- thread 1 get lock success thread 1 realse the lock thread 3 get lock fail thread 2 get lock success thread 2 realse the lock
2-4- lockInterruptibly()
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockInterruptiblyTest { public static void main(String[] args) { //1-开启第一个线程,期间会sleep 1秒 LockInterruptiblyDemo lockDemo = new LockInterruptiblyDemo(); new Thread(lockDemo,"thread 1").start(); //2-开启第二个线程,需要等待第一个线程释放锁,才能进入 Thread thread2 = new Thread(lockDemo, "thread 2"); thread2.start(); //3-开启第三个线程,用于中断第二个线程 LockInterruptDemo lockInterruptDemo = new LockInterruptDemo(thread2); new Thread(lockInterruptDemo,"thread 3").start(); //预计结果,第一个线程成功获取锁并释放锁,但第二个线程会被中断无法成功获取锁 } } //测试线程target class LockInterruptiblyDemo implements Runnable{ Lock lock = new ReentrantLock(); @Override public void run() { try { lock.lockInterruptibly(); try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+ " get lock success"); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName()+ " release lock success"); lock.unlock(); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " has been interrupted and get lock unsuccessfully"); } } } //用于中断其它线程的线程taget class LockInterruptDemo implements Runnable{ private Thread thread; public LockInterruptDemo(Thread thread) { this.thread = thread; } @Override public void run() { //中断目标线程 thread.interrupt(); System.out.println(Thread.currentThread().getName() +" run success "); System.out.println(Thread.currentThread().getName() +" has interrupted " + thread.getName() +"successfully"); } } ------------------------------------日志----------------------------------- thread 3 run success thread 2 has been interrupted and get lock unsuccessfully thread 3 has interrupted thread 2successfully thread 1 get lock success thread 1 release lock success
3- Lock和synchronized对比
1)synchronized是Java语言的关键字,因此是内置特性,Lock不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。
2)synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3)在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。
三、ReadWriteLock接口
1- ReadWriteLock接口简介
A ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writing.The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive.
ReadWriteLock提供了一对关联锁——只读锁和写入锁。只要没有写入操作,只读锁可以同时被多个线程使用;但是写入锁是唯一排他的
2- ReadWriteLock接口源码
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing */ Lock writeLock(); }
3- ReadWriteLock实现类是ReentrantReadWriteLock。其内容和ReentrantLock相似
4-使用示例代码
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockTest { public static void main(String[] args) { ReadWriteLockDemo th = new ReadWriteLockDemo(); //write new Thread(new Runnable() { @Override public void run() { th.set((int)(Math.random()*100)); } }).start(); //read for(int i=1; i<10; i++){ new Thread(new Runnable() { @Override public void run() { th.get(); } }).start(); } } } class ReadWriteLockDemo{ private int number = 0; private ReadWriteLock lock = new ReentrantReadWriteLock(); //write public void get(){ //获取只读锁 lock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + ": " + number); } finally { //释放只读锁 lock.readLock().unlock(); } } //read public void set(int number){ //获取写入锁 lock.writeLock().lock(); try { this.number = number; System.out.println(Thread.currentThread().getName() + " write: " +this.number); } finally { //释放写入锁 lock.writeLock().unlock(); } } } -------------------------------日志------------------------------------------------ Thread-0 write: 6 Thread-1: 6 Thread-2: 6 Thread-4: 6 Thread-5: 6 Thread-3: 6 Thread-7: 6 Thread-6: 6 Thread-8: 6 Thread-9: 6
四、几个概念
1- 可重入锁
如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
2- 可中断锁
可中断锁:顾名思义,就是可以相应中断的锁。
在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
在前面演示lockInterruptibly()的用法时已经体现了Lock的可中断性。
3- 公平锁
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
4- interrupt相关
interrupt()方法是用于中断线程的,调用该方法的线程的状态将被置为"中断"状态。注意:线程中断仅仅是设置线程的中断状态位,不会停止线程。所以当一个线程处于中断状态时,如果再由wait、sleep以及jion三个方法引起的阻塞,那么JVM会将线程的中断标志重新设置为false,并抛出一个InterruptedException异常,然后开发人员可以中断状态位“的本质作用-----就是程序员根据try-catch功能块捕捉jvm抛出的InterruptedException异常来做各种处理,比如如何退出线程。总之interrupt的作用就是需要用户自己去监视线程的状态位并做处理。”
END