共计 3521 个字符,预计需要花费 9 分钟才能阅读完成。
一、相似之处:Lock 锁 vs Synchronized 代码块
Lock 锁是一种相似于 synchronized 同步代码块的线程同步机制。从 Java 5 开始 java.util.concurrent.locks
引入了若干个 Lock 锁的实现类,所以通常状况下咱们不须要实现本人的锁,重要的是须要晓得如何应用它们,理解它们实现背地的原理。
Lock 锁 API 的根本应用办法和 Synchronized 关键字大同小异,代码如下
Lock lock = new ReentrantLock(); // 实例化锁
//lock.lock(); // 上锁
boolean locked = lock.tryLock(); // 尝试上锁
if(locked){
try {// 被锁定的同步代码块,同时只能被一个线程执行}finally {lock.unlock(); // 放在 finally 代码块中,保障锁肯定会被开释
}
}
synchronized(obj){// 被锁定的同步代码块,同时只能被一个线程执行}
Lock 锁应用看上去麻烦一点,然而 java 默认提供了很多 Lock 锁,能满足更多的利用场景。比方:基于信号量加锁、读写锁等等,关注我的专栏《java 并发编程》,后续都会介绍。
二、Lock 接口中的办法
Lock 接口实现办法通常会保护一个计数器,当计数器 = 0 的时候资源被开释,当计数器大于 1 的时候资源被锁定。
public interface Lock {void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();}
- lock() – 调用该办法会使锁定计数器减少 1,如果此时共享资源是闲暇的,则将锁交给调用该办法的线程。
- unlock() – 调用该办法使锁定计数器缩小 1,当锁定计数器为 0 时,资源被开释。
- tryLock() – 如果该资源是闲暇的,那么调用该办法将返回 true,锁定计数器将减少 1。如果资源处于被占用状态,那么该办法返回 false,然而线程将不被阻塞。
- tryLock(long timeout, TimeUnit unit) – 依照该办法尝试取得锁,如果资源此时被占用,线程在退出前期待肯定的时间段,该时间段由该办法的参数定义,以冀望在此工夫内取得资源锁。
- lockInterruptibly() – 如果资源是闲暇的,该办法会获取锁,同时容许线程在获取资源时被其余线程打断。这意味着,如果以后线程正在期待一个锁,但其余线程要求取得该锁,那么以后线程将被中断,并立刻返回不会取得锁。
三、不同点:Lock 锁 vs Synchronized 代码块
应用 synchronized 同步块和应用 Lock API 之间还是有一些区别的
- 一个 synchronized 同步块必须齐全蕴含在一个办法中 – 但 Lock API 的 lock()和 unlock()操作,能够在不同的办法中进行
- synchronized 同步块不反对公平性准则,任何线程都能够在开释后从新取得锁,不能指定优先级。但咱们能够通过指定 fairness 属性在 Lock API 中实现偏心的优先级,能够实现等待时间最长的线程被赋予对锁的占有权。
- 如果一个线程无法访问 synchronized 同步块,它就会被阻塞期待。Lock API 提供了 tryLock()办法,尝试获取锁对象,获取到锁返回 true,否则返回 false。返回 false 并不阻塞线程,所以应用该办法能够缩小期待锁的线程的阻塞工夫。
四、锁的可重入性
”可重入“意味着某个线程能够平安地屡次取得同一个锁对象,而不会造成死锁。
4.1. synchronized 锁的可重入性
上面的代码 synchronized 代码块嵌套 synchronized 代码块,锁定同一个 this 对象,不会产生死锁。证实synchronized 代码块针对同一个对象加锁,是可重入的。
public void testLock(){synchronized (this) {System.out.println("第 1 次获取锁,锁对象是:" + this);
int index = 1;
do {synchronized (this) {System.out.println("第" + (++index) + "次获取锁,锁对象是:" + this);
}
} while (index != 10);
}
}
下面的这段代码输入后果是
第 1 次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第 2 次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第 3 次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第 4 次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第 5 次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第 6 次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第 7 次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第 8 次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第 9 次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
第 10 次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
4.2.ReentrantLock 可重入锁
Lock 接口的实现类 ReentrantLock,也是可重入锁。一般来说类名蕴含 Reentrant 的 Lock 接口实现类实现的锁都是可重入的。
public void testLock1(){Lock lock = new ReentrantLock(); // 实例化锁
lock.lock(); // 上锁
System.out.println("第 1 次获取锁,锁对象是:" + lock);
try {
int index = 1;
do {lock.lock(); // 上锁
try {System.out.println("第" + (++index) + "次获取锁,锁对象是:" + lock);
}finally {lock.unlock();
}
} while (index != 10);
}finally {lock.unlock(); // 放在 finally 代码块中,保障锁肯定会被开释
}
}
当线程第一次取得锁的时候,计数器被设置为 1。在解锁之前,该线程能够再次取得锁 ,每次计数器都会减少 1。对于每一个解锁操作,计数器被递加 1,当计数器为 0 时锁定资源被开释。所以最重要的是:lock(tryLock) 要与 unlock 办法成对呈现,即:在代码中加锁一次就必须解锁一次,否则就死锁
五、Lock 锁的公平性
Java 的 synchronized 同步块对试图进入它们的线程,被授予拜访权(占有权)的优先级程序没有任何保障。因而如果许多线程一直抢夺对同一个 synchronized 同步块的拜访权,就有可能有一个或多个线程从未被授予拜访权。这就造成了所谓的 “线程饥饿“。为了防止这种状况,锁应该是偏心的。
Lock lock = new ReentrantLock(true);
可重入锁提供了一个公平性参数 fairness,通过该参数 Lock 锁将恪守锁申请的程序,即在一个线程解锁资源后,锁将被交给等待时间最长的线程。这种偏心模式是通过在锁的构造函数中传递 “true “ 来设置的。
欢送关注我的博客,更多精品常识合集
本文转载注明出处(必须带连贯,不能只转文字):字母哥博客 – zimug.com
感觉对您有帮忙的话,帮我点赞、分享!您的反对是我不竭的创作能源!。另外,笔者最近一段时间输入了如下的精品内容,期待您的关注。
- 《kafka 修炼之道》
- 《手摸手教你学 Spring Boot2.0》
- 《Spring Security-JWT-OAuth2 一本通》
- 《实战前后端拆散 RBAC 权限管理系统》
- 《实战 SpringCloud 微服务从青铜到王者》