java多线程Lock接口简介使用与synchronized对比 多线程下篇(三)

前面的介绍中,对于显式锁的概念进行了简单介绍
显式锁的概念,是基于JDK层面的实现,是接口,经过这个接口能够实现同步访问
而不一样于synchronized关键字,他是Java的内置特性,是基于JVM的实现
image_5c7f20d6_7841
Lock接口的核心概念很简单,只有以下几个方法
image_5c7f20d6_1834
按照逻辑能够进行以下划分
image_5c7f20d6_415

lock()

Lock接口,因此synchronized关键字更为灵活的一种同步方案,在实际使用中,天然是可以替代synchronized关键字的
(ps:尽管你不须要老是使用显式锁,显式锁与隐式锁各有利弊,可是在语法上是的确能够替代的)
synchronized关键字是阻塞式的获取锁
lock方法就是这一逻辑的体现,也就是说对于lock()方法,若是获取不到锁,那么将会进入阻塞状态,与synchronized关键字同样

lockInterruptibly()

Lock()方法是一种阻塞式的,另外Lock接口还提供了可中断的lock获取方法,先看下测试例子
package test2;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class T28 {
private static final Lock LOCK = new ReentrantLock();
public static void main(String[] args) {
//线程A获取加锁以后,持有五秒钟
Thread threadA = new Thread(() -> {
LOCK.lock();
try {
System.out.println(Thread.currentThread().getName() + " " + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName() + " sleep");
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread().getName() + " " + System.currentTimeMillis());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupt");
} finally {
LOCK.unlock();
}
}, "thread-A");
threadA.start();
//线程B开始后,尝试获取锁
Thread threadB = new Thread(() -> {
LOCK.lock();
try {
System.out.println(Thread.currentThread().getName() + " " + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName() + " working");
} finally {
LOCK.unlock();
}
}, "thread-B");
threadB.start();
//为了确保上面的任务都开始了,主线程sleep 1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
threadB.interrupt();
}
}
示例逻辑
两个线程A和B,使用同一把锁
A线程获取锁后,休眠10s,紧接着B尝试获取锁
为了保证前面的任务都开始了,主线程sleep 1s后,将线程B进行中断
对于lock方法,如同synchronized关键字,是阻塞式的,经过执行来看,能够发现,在A持有锁期间,线程B也是一直阻塞的,是不可以获取到锁,也不能被中断(上面示例中调用interrupt()没有任何的反应)
image_5c7f20d6_4536
将代码稍做修改,也就是将lock方法修改成lockInterruptibly()方法,其余暂时不变
image_5c7f20d6_7bb5
再次运行,你会发现立刻就被中断了,而不是傻傻的等待A结束
固然,由于根本都没有获取到锁,因此在finally中尝试unlock时,将会抛出异常,这个暂时无论了,经过这个例子能够看得出来
对于lockInterruptibly方法,这是一个“可中断的锁获取操做
image_5c7f20d6_1200
小结
lockInterruptibly就是一个可中断的锁获取操做,在尝试获取锁的过程当中,若是不可以获取到,若是被中断,那么它将可以感知到这个中断,而不是一直阻塞下去
若是锁不可用(被其余线程持有),除非发生如下事件,不然将会等待
  • 该线程成功得到锁
  • 发生中断
若是当前线程遇到下面的事件,则将抛出 InterruptedException,并清除当前线程的已中断状态。
  • 在进入此方法时已经设置了该线程的中断状态
  • 在获取锁时被中断
从上面的分析能够看得出来,若是什么都没发生,这个方法与lock方法并无什么区别,就是在等待获取锁,获取不到将会阻塞
他只是额外的对可中断提供了支持  

unlock()

unlock并无什么特殊的,他替代了synchronized关键字隐式的解锁操做
一般须要在finally中确保unlock操做会被执行,以前提到过,对于synchronized关键字解锁是隐式的,也是必然的,即便出现错误,JVM也会保障可以正确的解锁
可是对于Lock接口提供的unlock操做,则必须本身确保可以正确的解锁  

tryLock()

相对于synchronized,Lock接口另外一大改进就是try lock
顾名思义,尝试获取锁,既然是尝试,那显然并不会势在必得
tryLock方法就是一次尝试,若是锁可用,则获取锁,并当即返回值 true。若是锁不可用,则此方法将当即返回值 false
也就是说方法会当即返回,若是获取到锁返回true,不然返回false,无论如何都是立马返回
典型的用法就是以下所示,下面的代码还可以确保若是没有获取锁,不会试图进行unlock操做
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
tryLock只是一次尝试,若是你须要不断地进行尝试,那么可使用while替代if的条件判断
尽管tryLock只是一次的测试,可是能够借助于循环(有限或者无限)进行屡次测试  

tryLock(long time, TimeUnit unit)

对于TryLock还有可中断、配置超时时间的版本
boolean tryLock(long time,
                TimeUnit unit)
                throws InterruptedException
两个参数,第一个为值,第二个为第一个参数的单位,好比1,单位秒,或者2 ,单位分钟
在指定的超时时间内,若是可以获取到锁,那么将会返回true;
若是超过了指定的时间,可是却不能获取到锁,那么将会返回false;
另外很显然,这个方法是可中断的,也就是说若是尝试过程当中,出现了中断,那么他将会抛出InterruptedException
因此,对于这个方法,他会一直尝试获取锁(也能够认为是必定时长内的“阻塞”,固然能够被中断),除非:
  • 该线程成功得到锁
  • 超过了超时时长
  • 该线程被中断
能够认为是lockInterruptibly的限时版本
若是没有发生中断,也认为他就是“定时版本的lock()”
无论怎么理解,只须要记住:他会在必定时长内尝试进行锁的获取,也支持中断

锁小结

对于lock方法和unlock方法,就是相似于synchronized关键字的加锁和解锁,并无什么特别的
其余几个方法是Lock接口针对于锁获取的阻塞以及可中断两个方面进行了拓展
隐式锁的阻塞以及不可中断,致使一旦开始尝试获取,那么则没办法唤醒,将会一直等待,除非得到
  • lockInterruptibly()是阻塞式的,若是获取不到会一直等待,可是他是可中断的,可以经过阻塞打破这种等待
  • tryLock()不会进行任何阻塞,只是尝试获取一下,能获取到就获取,获取不到就false,拉倒
  • tryLock(long time, TimeUnit unit),便是可中断的,又是限时阻塞的,即便不中断,也不会一直阻塞,即便处于阻塞中(超时时长还没到),也能够随时中断
对于lockInterruptibly()方法以及tryLock(long time, TimeUnit unit),都支持中断,可是须要注意:
在某些实现中可能没法中断锁获取,即便可能,该操做的开销也很大  

Condition

在隐式锁的逻辑中,借助于Java底层机制,每一个对象都有一个相关联的锁与监视器
对于synchronized的隐式锁逻辑就是借助于锁与监视器,从而进行线程的同步与通讯协做
在显式锁中,Lock接口提供了synchronized的语意,对于监视器的概念,则借助于Condition,可是很显然,Condition也是与锁关联的
Lock接口提供了方法Condition newCondition();
Condition也是一个接口,他定义了相关的监视器方法
在显式锁中,能够定义多个Condition,也就是一个锁,能够对应多个监视器,能够更加细粒度的进行同步协做的处理

总结

Lock接口提供了相对于synchronized关键字,而更为灵活的一种同步手段
它的核心与本质仍旧是为了线程的同步与协做通讯
因此它的核心仍旧是锁与监视器,也就是Lock接口与Condition接口
可是灵活是有代价的,因此并不须要在全部的地方都尝试使用显式锁,若是场景知足须要,synchronized仍旧是一种很好的解决方案(也是应该被优先考虑的一种方式)
与synchronized再次对比下
  • synchronized是JVM底层实现的,Lock是JDK接口层面的
  • synchronized是隐式的,Lock是显式的,须要手动加锁与解锁
  • synchronized乌不管如何都会释放,即便出现错误,Lock须要本身保障正确释放
  • synchronized是阻塞式的获取锁,Lock能够阻塞获取,可中断,还能够尝试获取,还能够设置超时等待获取
  • synchronized没法判断锁的状态,Lock能够进行判断
  • synchronized可重入,不可中断,非公平,Lock可重入,可中断、可配置公平性(公平和非公平均可以)
  • 若是竞争不激烈,二者的性能是差很少的,但是synchronized的性能还在不断的优化,当竞争资源很是激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized   
  • 等   
对于Lock接口,他仍旧是一个对象,因此他是否能够用来做为锁以及调用监视器方法(用在synchronized(lock)中)?
这逻辑上是没问题的,可是最好不要那么作,由于很容易引发混淆的,不论是维护上仍是易读性上都有很大的问题
在lock上调用他的监视器方法,与借助于lock实现线程的同步,本质上是没有什么关系的
image_5c7f20d6_6b7b
尽管看起来Lock是那么的优秀,可是仍是要再次提醒,除非synchronized真的不行,不然你应该使用synchronized而不是Lock
相关文章
相关标签/搜索