显式锁
有了 synchronized 为什么还要 Lock? Java 程序是靠 synchronized 关键字实现锁功能的,使用 synchronized 关键字 将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。
Synchronized 关键字结合对象的监视器,JVM 为我们提供了一种『内置锁』的语义,这种锁很简便,不需要我们关心加锁和释放锁的过程,我们只需要告诉虚拟机哪些代码块需要加锁即可,其他的细节会由编译器和虚拟机自己实现。
可以将我们的『内置锁』理解为是 JVM 的一种内置特性,它不支持某些高级功能的定制,比如说,我想要这个锁支持公平竞争,我想要根据不同的条件将线程阻塞在不同的队列上,我想要支持定时竞争锁,超时返回,我还想让被阻塞的线程能够响应中断请求等。
这些特殊的需求是『内置锁』满足不了的,所以在 JDK 层面又引入了『显式锁』的概念,不再由 JVM 来负责加锁和释放锁,这两个动作释放给我们程序来做,程序层面难免复杂了些,但锁灵活性提高了,可以支持更多定制功能,但要求你对锁具有更深层次的理解。
Lock的标准用法
在 finally 块中释放锁,目的是保证在获取到锁之后,最终能够被释放。 不要将获取锁的过程写在 try 块中,因为如果在获取锁(自定义锁的实现) 时发生了异常,异常抛出的同时,也会导致锁无故释放。
Lock的常用API
注:
无特殊需求(线程取锁等待中断),推荐使用synchronized关键字,从资源消耗上来讲,Lock是一个类,需要消耗内存,而synchronized关键字是程序的特写,无需new出一个对象,资源相对消耗较小。
ReentrantLock 可重入锁 (Lock的实现类)
锁的可重入
简单地讲就是:“同一个线程对于已经获得到的锁,可以多次继续申请到该 锁的使用权”。而 synchronized 关键字隐式的支持重进入,比如一个 synchronized 修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得 该锁。ReentrantLock 在调用 lock()方法时,已经获取到锁的线程,能够再次调用 lock()方法获取锁而不被阻塞
公平和非公平锁
如果在时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的, 反之,是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁, 也可以说锁获取是顺序的。 ReentrantLock 提供了一个构造函数,能够控制锁是 否是公平的。事实上,公平的锁机制往往没有非公平的效率高。 在激烈竞争的情况下,非公平锁的性能高于公平锁的性能的一个原因是:在恢 复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。假设线程 A 持有一个锁,并且线程 B 请求这个锁。由于这个锁已被线程 A 持有,因此 B 将被挂 起。当 A 释放锁时,B 将被唤醒,因此会再次尝试获取锁。与此同时,如果 C 也请求 这个锁,那么 C 很可能会在 B 被完全唤醒之前获得、使用以及释放这个锁。这样 的情况是一种“双赢”的局面:B 获得锁的时刻并没有推迟,C 更早地获得了锁,并 且吞吐量也获得了提高。
注 :ReentrantLock 默认是非公平锁,ReentrantLock 通过构造方法可设置公平锁,synchronized是为非公平锁,非公平锁的性能要比公平锁的高, ReentrantLock 以及 synchronized也叫独占锁
ReentrantLock 代码演示
/**
* 类说明:使用Lock的范例
*/
public class LockCase {
private Lock lock = new ReentrantLock();
private int age = 100000;//初始100000
private static class TestThread extends Thread {
private LockCase lockCase;
public TestThread(LockCase lockCase, String name) {
super(name);
this.lockCase = lockCase;
}
@Override
public void run() {
for (int i = 0; i < 100000; i++) {//递增100000
lockCase.test();
}
System.out.println(Thread.currentThread().getName()
+ " age = " + lockCase.getAge());
}
}
public void test() {
lock.lock();
try {
age++;
} finally {
lock.unlock();
}
}
public void test2() {
lock.lock();
try {
age--;
} finally {
lock.unlock();
}
}
public int getAge() {
return age;
}
public static void main(String[] args) throws InterruptedException {
LockCase lockCase = new LockCase();
Thread endThread = new TestThread(lockCase, "endThread");
endThread.start();
for (int i = 0; i < 100000; i++) {//递减100000
lockCase.test2();
}
System.out.println(Thread.currentThread().getName()
+ " age = " + lockCase.getAge());
}
}
读写锁ReentrantReadWriteLock
之前提到锁(如 Mutex 和 ReentrantLock)基本都是排他锁,这些锁在同一 时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问, 但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对 锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁 有了很大提升。
除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁能够简化 读写交互场景的编程方式。假设在程序中定义一个共享的用作缓存数据结构,它 大部分时间提供读服务(例如查询和搜索),而写操作占有的时间很少,但是写 操作完成之后的更新需要对后续的读服务可见。
在没有读写锁支持的(Java5 之前)时候,如果需要完成上述工作就要使用 Java 的等待通知机制,就是当写操作开始时,所有晚于写操作的读操作均会进入 等待状态,只有写操作完成并进行通知之后,所有等待的读操作才能继续执行(写 操作之间依靠 synchronized 关键进行同步),这样做的目的是使读操作能读取到 正确的数据,不会出现脏读。改用读写锁实现上述功能,只需要在读操作时获取 读锁,写操作时获取写锁即可。当写锁被获取到时,后续(非当前写操作线程) 的读写操作都会被阻塞,写锁释放之后,所有操作继续执行,编程方式相对于使 用等待通知机制的实现方式而言,变得简单明了。
一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。 在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量
ReentrantReadWriteLock 其实实现的是 ReadWriteLock 接口
废话不多说上代码
实现读写锁的逻辑
/**
* 类说明: 读写锁
*/
public class UseRwLock implements GoodsService{
private GoodsInfo goodsInfo;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock getLock = lock.readLock();//读锁
private final Lock setLock = lock.writeLock();//写锁
public UseRwLock(GoodsInfo goodsInfo) {
this.goodsInfo = goodsInfo;
}
@Override
public GoodsInfo getNum() {
//获取读锁
getLock.lock();
try{
SleepTools.ms(5);
//TODO 我们平时的查询接口
return this.goodsInfo;
}finally {
getLock.unlock();
}
}
@Override
public void setNum(int number) {
//获取写锁
setLock.lock();
try{
SleepTools.ms(5);
//TODO 我们平时的添加接口
goodsInfo.changeNumber(number);
}finally {
setLock.unlock();
}
}
}
调用
/**
*类说明:对商品进行业务的应用
*/
public class BusiApp {
static final int readWriteRatio = 10;//读写线程的比例
static final int minthreadCount = 3;//最少线程数
//读操作
private static class GetThread implements Runnable{
private GoodsService goodsService;
public GetThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
long start = System.currentTimeMillis();
for(int i=0;i<100;i++){//操作100次
goodsService.getNum();
}
System.out.println(Thread.currentThread().getName()+"读取商品数据耗时:"
+(System.currentTimeMillis()-start)+"ms");
}
}
//写操做
private static class SetThread implements Runnable{
private GoodsService goodsService;
public SetThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
long start = System.currentTimeMillis();
Random r = new Random();
for(int i=0;i<10;i++){//操作10次
SleepTools.ms(50);
goodsService.setNum(r.nextInt(10));
}
System.out.println(Thread.currentThread().getName()
+"写商品数据耗时:"+(System.currentTimeMillis()-start)+"ms---------");
}
}
public static void main(String[] args) throws InterruptedException {
GoodsInfo goodsInfo = new GoodsInfo("Cup",100000,10000);
GoodsService goodsService = new UseRwLock(goodsInfo);
for(int i = 0;i<minthreadCount;i++){
Thread setT = new Thread(new SetThread(goodsService));
for(int j=0;j<readWriteRatio;j++) {
Thread getT = new Thread(new GetThread(goodsService));
getT.start();
}
SleepTools.ms(100);
setT.start();
}
}
}
Condition接口
Condition常 用 方 法
Condition使 用 范 式
Condition使 用
与 等待通知的范式wait()、notify()相似
/**
*类说明:Condition等待通知演示
*/
public class ExpressCond {
public final static String CITY = "ShangHai";
private int km;/*快递运输里程数*/
private String site;/*快递到达地点*/
private Lock kmLock = new ReentrantLock();
private Lock siteLock = new ReentrantLock();
private Condition kmCond = kmLock.newCondition();
private Condition siteCond = siteLock.newCondition();
public ExpressCond() {
}
public ExpressCond(int km, String site) {
this.km = km;
this.site = site;
}
/* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
public void changeKm(){
kmLock.lock();
try{
this.km = 101;
kmCond.signal();
//kmCond.signalAll();
}finally {
kmLock.unlock();
}
}
/* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
public void changeSite(){
siteLock.lock();
try {
this.site = "BeiJing";
siteCond.signal();//通知其他在锁上等待的线程
}finally {
siteLock.unlock();
}
}
/*当快递的里程数大于100时更新数据库*/
public void waitKm(){
kmLock.lock();
try{
while(this.km<100){
try {
kmCond.await();
System.out.println("Check Site thread["
+Thread.currentThread().getId()
+"] is be notified");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
kmLock.unlock();
}
System.out.println("the Km is "+this.km+",I will change db");
}
/*当快递到达目的地时通知用户*/
public void waitSite(){
siteLock.lock();
try {
while(this.site.equals(CITY)) {
try {
siteCond.await();//当前线程进行等待
System.out.println("check Site thread["+Thread.currentThread().getName()
+"] is be notify");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}finally {
siteLock.unlock();
}
System.out.println("the site is "+this.site+",I will call user");
}
}
/**
*类说明:单锁的实现 等待通知
*/
public class ExpressCondOneLock {
public final static String CITY = "ShangHai";
private int km;/*快递运输里程数*/
private String site;/*快递到达地点*/
private Lock lock = new ReentrantLock();
private Condition kmCond = lock.newCondition();
private Condition siteCond = lock.newCondition();
public ExpressCondOneLock() {
}
public ExpressCondOneLock(int km, String site) {
this.km = km;
this.site = site;
}
/* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
public void changeKm(){
lock.lock();
try {
this.km = 101;
kmCond.signal();//通知其他在锁上等待的线程
}finally {
lock.unlock();
}
}
/* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
public void changeSite(){
lock.lock();
try {
this.site = "BeiJing";
siteCond.signal();//通知其他在锁上等待的线程
}finally {
lock.unlock();
}
}
/*当快递的里程数大于100时更新数据库*/
public void waitKm(){
lock.lock();
try {
while(this.km<100) {
try {
kmCond.await();//当前线程进行等待
System.out.println("check km thread["+Thread.currentThread().getName()
+"] is be notify");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}finally {
lock.unlock();
}
System.out.println("the Km is "+this.km+",I will change db");
}
/*当快递到达目的地时通知用户*/
public void waitSite(){
lock.lock();
try {
while(this.site.equals(CITY)) {
try {
siteCond.await();//当前线程进行等待
System.out.println("check Site thread["+Thread.currentThread().getName()
+"] is be notify");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}finally {
lock.unlock();
}
System.out.println("the site is "+this.site+",I will call user");
}
}
最后为测试类
/**
* 类说明:测试Lock和Condition实现等待通知
*/
public class TestCond {
private static ExpressCond express = new ExpressCond(0, ExpressCond.CITY);
/*检查里程数变化的线程,不满足条件,线程一直等待*/
private static class CheckKm extends Thread {
@Override
public void run() {
express.waitKm();
}
}
/*检查地点变化的线程,不满足条件,线程一直等待*/
private static class CheckSite extends Thread {
@Override
public void run() {
express.waitSite();
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new CheckSite().start();
}
for (int i = 0; i < 3; i++) {
new CheckKm().start();
}
Thread.sleep(1000);
express.changeKm();//快递里程变化
}
}