听说你不会Lock,我发了3个夜晚写给你
我们知道 synchronized 是java内部关键字,比较重量级的独占锁,好处就是使用方便,不需要手动释放锁;然而
Lock 则需要手动加锁,手动释放锁;
一ReentrantLock使用
ReentrantLock 意为可重入锁,方法预览如下
//创建一个 ReentrantLock 的实例
ReentrantLock()
//创建一个具有给定公平策略的 ReentrantLock
ReentrantLock(boolean fair)
//查询当前线程持有锁的个数
int getHoldCount()
//返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null
protected Thread getOwner()
//返回一个collection,它包含可能正等待获取此锁的线程
protected Collection<Thread> getQueuedThreads()
int getQueueLength() //返回正等待获取此锁的线程估计数
//返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程
protected Collection<Thread> getWaitingThreads(Condition condition)
//返回等待与此锁相关的给定条件的线程估计数
int getWaitQueueLength(Condition condition)
//查询给定线程是否正在等待获取此锁
boolean hasQueuedThread(Thread thread)
//查询是否有些线程正在等待获取此锁
boolean hasQueuedThreads()
//查询是否有些线程正在等待与此锁有关的给定条件
boolean hasWaiters(Condition condition)
//如果此锁的公平设置为 true,则返回true
boolean isFair()
//查询当前线程是否保持此锁
boolean isHeldByCurrentThread()
//查询此锁是否由任意线程保持
boolean isLocked()
//获取锁
void lock()
//如果当前线程未被中断,则获取锁。
void lockInterruptibly()
//返回用来与此 Lock 实例一起使用的 Condition 实例
Condition newCondition()
//仅在调用时锁未被另一个线程保持的情况下,才获取该锁
boolean tryLock()
//如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁
boolean tryLock(long timeout, TimeUnit unit)
//释放此锁
void unlock()
1.1Lock与unLock 简单使用
使用 lock() 加锁, unlock()释放锁;
public class RLook {
private Lock lock = new ReentrantLock();
private void testLock(){
// 加锁
lock.lock();
// 执行业务逻辑
for (int i=0; i<8; i++){
System.out.println(i+"==="+Thread.currentThread().getName());
}
// 释放锁
lock.unlock();
}
public static void main(String[] args) {
// 线程1
RLook rLook = new RLook();
new Thread(()-> {
rLook.testLock();
}).start();
// 线程2
new Thread(()-> {
rLook.testLock();
}).start();
}
}
输出如下,不同线程之间应是分组打印;
0===Thread-0
1===Thread-0
2===Thread-0
3===Thread-0
4===Thread-0
5===Thread-0
6===Thread-0
7===Thread-0
0===Thread-1
1===Thread-1
2===Thread-1
3===Thread-1
4===Thread-1
5===Thread-1
6===Thread-1
7===Thread-1
1.2Lock与unLock 正确使用方式
上面的代码有个缺点,如果在执行业务代码的时候发生了异常就会发生死锁的现象,所以通常情况下我们会将业务代码放在try{},catch{} 代码块中, 最后使用 finally 释放锁;
代码格式应如下
Lock lock =new ReentrantLock();
....
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
....
1.3源码角度说明Lock
public interface Lock {
// 加锁
void lock();
// 中断
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁
boolean tryLock();
// 尝试获取锁
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 信号通知
Condition newCondition();
}
-
lock()方法是用来获取锁。如果锁已被其他线程获取,则进行锁等待。采用Lock必须手动释放锁,并且在发生异常时,不会自动释放锁,所以需要放在try,cath代码块中, 最后使用 finally 释放锁;
-
tryLock()方法用来表示尝试获取锁,如果获取成功,则返回true,如果获取失败,则返回false;
-
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,区别在于可以设置锁等待时间,在等待时间内为获取到锁,则返回false,获取到则返回true;
Lock lock=new ReentrantLock();
....
if(lock.tryLock()) {
try{
//业务逻辑
}catch(Exception ex){
}finally{
//释放锁
lock.unlock();
}
}else {
// 未获取锁逻辑
}
- lockInterruptibly()为中断方法,当通过这个方法去获取锁时,如果线程正在处于获取锁状态,则该线程能够响应中断(中断线程的等待状态),抛出中断异常。也就使说,当两个线程同时通过lock.lockInterruptibly()获取某个锁时,如果线程A获取到了锁,而线程B在等待状态,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待状态;
- Condition类是JDK5的新API,可以实现多路通知功能,一个Lock对象可以创建多个Condition, 通过注入不同的Condition灵活实现不同的线程通知功能;然而synchronized 的 wait(), notify(), notify All() 通知机制是随机无法实现选择性通知功能;
1.4使用lockInterruptibly
public class InterruptTest {
private Lock lock = new ReentrantLock();
public void interrupt() throws InterruptedException {
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + "得到了锁");
long startTime = System.currentTimeMillis();
for (; ; ) {
if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
break;
}
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
public static void main(String[] args) {
// 线程1
InterruptTest rLook = new InterruptTest();
Thread threadA = new Thread(() -> {
try {
rLook.interrupt();
} catch (InterruptedException e) {
System.out.println("线程A进行了中断");
e.printStackTrace();
}
});
// 线程2
Thread threadB = new Thread(() -> {
try {
rLook.interrupt();
} catch (InterruptedException e) {
System.out.println("线程B进行了中断");
e.printStackTrace();
}
});
threadA.setName("A");
threadB.setName("B");
threadA.start();
threadB.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadB.interrupt();
}
}
输出结果如下,线程A通过中断获取锁,线程B再通过中断获取锁时处于等待状态,直接中断抛出异常;
A得到了锁
线程B进行了中断
1.5使用Condition实现等待通知机制
codition 接口主要方法如下
//造成当前线程在接到信号或被中断之前一直处于等待状态。
void await()
//造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
boolean await(long time, TimeUnit unit)
//造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
long awaitNanos(long nanosTimeout)
//造成当前线程在接到信号之前一直处于等待状态。
void awaitUninterruptibly()
//造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
boolean awaitUntil(Date deadline)
//唤醒一个等待线程。
void signal()
//唤醒所有等待线程。
void signalAll()
利用 Condition 的await() 方法和 signal() 方法 实现 等待通知机制;
public class ConditionTest {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// 线程等待
private void await(){
try {
// 加锁
lock.lock();
System.out.println("await时间为:"+System.currentTimeMillis());
// 等待
condition.await();
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁
lock.unlock();
}
}
// 线程唤醒
public void signal() {
lock.lock();
try {
System.out.println("signal时间为" + System.currentTimeMillis());
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
// 线程1
ConditionTest rLook = new ConditionTest();
Thread thread = new Thread(() -> {
rLook.await();
});
thread.start();
//
thread.sleep(3000);
//
rLook.signal();
}
}
输出输出结果如下
await时间为:1606707478993
signal时间为1606707481993
多个Condition 使用方式
多个Condition 使用时,互不干涉; 线程 A, B 使用 不同Condition 的都 进入等待状态, 当使用 对应的 Condition 的 singal 才唤醒对应的线程;
public class MultiCondition {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
// 线程等待
private void awaitA(){
try {
// 加锁
lock.lock();
System.out.println("线程"+Thread.currentThread().getName()+"----await时间为:"+System.currentTimeMillis());
// 等待
conditionA.await();
System.out.println("线程"+Thread.currentThread().getName()+"继续执行");
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁
lock.unlock();
}
}
// 线程等待
private void awaitB(){
try {
// 加锁
lock.lock();
System.out.println("线程"+Thread.currentThread().getName()+"----await时间为:"+System.currentTimeMillis());
// 等待
conditionB.await();
System.out.println("线程"+Thread.currentThread().getName()+"继续执行");
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁
lock.unlock();
}
}
// 线程唤醒
public void signalAllA() {
lock.lock();
try {
System.out.println("线程"+Thread.currentThread().getName()+"----signal时间为" + System.currentTimeMillis());
conditionA.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 线程唤醒
public void signalAllB() {
lock.lock();
try {
System.out.println("线程"+Thread.currentThread().getName()+"----signal时间为" + System.currentTimeMillis());
conditionB.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
// 线程1
MultiCondition rLook = new MultiCondition();
Thread threadA = new Thread(() -> {
rLook.awaitA();
});
threadA.setName("A");
threadA.start();
Thread threadB = new Thread(() -> {
rLook.awaitB();
});
threadB.setName("B");
threadB.start();
//
Thread.sleep(3000);
//
rLook.signalAllA();
}
}
输出结果如下,线程Condition A 对应的线程会被唤醒,B 线程 还是处于等待状态;
线程A----await时间为:1606708895650
线程B----await时间为:1606708895650
线程main----signal时间为1606708898651
线程A继续执行
1.6使用Condition实现生产消费模式
public class ConsumerProductCondition {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Boolean has = false;
// 生产
private void set(){
try {
// 加锁
lock.lock();
while (has == true){
condition.await();
}
System.out.println("生产GG");
has = true;
// 唤醒一个线程
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁
lock.unlock();
}
}
// 消费
private void get(){
try {
// 加锁
lock.lock();
while (has == false){
condition.await();
}
System.out.println("消费MM");
has = false;
// 唤醒一个线程
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
// 线程1
ConsumerProductCondition rLook = new ConsumerProductCondition();
Thread threadA = new Thread(() -> {
for (int i = 0; i < 200; i++) {
rLook.set();
}
});
// 线程2
Thread threadB = new Thread(() -> {
for (int i = 0; i < 200; i++) {
rLook.get();
}
});
threadA.start();
threadB.start();
}
}
输出交替打印
生产GG
消费MM
生产GG
消费MM
生产GG
消费MM
生产GG
消费MM
生产GG
消费MM
.....
1.7 公平锁与非公平锁
Lock 锁分为公平锁和非公平锁,公平锁表示通过线程加锁的顺序获取锁,非公平锁表示通过抢占机制获取锁;
公平锁获示例
public class FairLockTest {
private Lock lock = new ReentrantLock(true);
private void testLock(){
// 加锁
lock.lock();
// 执行业务逻辑
System.out.println("线程"+Thread.currentThread().getName()+"获取到锁");
// 释放锁
lock.unlock();
}
public static void main(String[] args) {
// 线程1
FairLockTest rLook = new FairLockTest();
for (int i = 0; i <100 ; i++) {
// 线程2
Thread thread = new Thread(() -> {
rLook.testLock();
});
thread.setName(""+i);
thread.start();
}
}
}
输出结果基本有序
线程0获取到锁
线程1获取到锁
线程2获取到锁
线程3获取到锁
线程4获取到锁
线程5获取到锁
线程6获取到锁
线程8获取到锁
线程7获取到锁
线程9获取到锁
线程10获取到锁
....
非公平锁示例如下
public class FairLockTest {
private Lock lock = new ReentrantLock(false);
private void testLock(){
// 加锁
lock.lock();
// 执行业务逻辑
System.out.println("线程"+Thread.currentThread().getName()+"获取到锁");
// 释放锁
lock.unlock();
}
public static void main(String[] args) {
// 线程1
FairLockTest rLook = new FairLockTest();
for (int i = 0; i <100 ; i++) {
// 线程2
Thread thread = new Thread(() -> {
rLook.testLock();
});
thread.setName(""+i);
thread.start();
}
}
}
输出结果基本无序
线程0获取到锁
线程1获取到锁
线程7获取到锁
线程2获取到锁
线程10获取到锁
线程3获取到锁
线程6获取到锁
线程11获取到锁
线程4获取到锁
线程5获取到锁
.........
二 ReentrantReadWriteLock 使用
ReentrantLock 与 synchronized 都是 独占锁, 安全性较高,但是相对来说效率低下;ReentrantReadWriteLock读写锁提供了 readLock()和writeLock()用来获取读锁和写锁; 读锁之间读取数据不互斥(故读锁也成为共享锁)。读锁写锁之间互斥,写锁写锁之间互斥;
2.1读锁
读锁示例
public class ReadLockTest {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private void testLock(){
// 加锁
lock.readLock().lock();
// 执行业务逻辑
System.out.println("线程"+Thread.currentThread().getName()+"获取到锁"+System.currentTimeMillis());
// 睡眠,保证B线程进来时A线程还是获取锁状态
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 释放锁
lock.readLock().unlock();
}
public static void main(String[] args) {
// 线程1
ReadLockTest rLook = new ReadLockTest();
// 线程2
Thread threadA = new Thread(() -> {
rLook.testLock();
});
threadA.setName("A");
Thread threadB = new Thread(() -> {
rLook.testLock();
});
threadB.setName("B");
threadA.start();
threadB.start();
}
}
线程A与线程B几乎时同时获取到锁
线程B获取到锁1606723001641
线程A获取到锁1606723001641
2.2 写锁
写锁示例
public class WriteLockTest {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private void testLock(){
// 加锁
lock.writeLock().lock();
// 执行业务逻辑
System.out.println("线程"+Thread.currentThread().getName()+"获取到锁"+System.currentTimeMillis());
// 睡眠,保证B线程进来时A线程还是获取锁状态
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 释放锁
lock.writeLock().unlock();
}
public static void main(String[] args) {
// 线程1
WriteLockTest rLook = new WriteLockTest();
// 线程2
Thread threadA = new Thread(() -> {
rLook.testLock();
});
threadA.setName("A");
Thread threadB = new Thread(() -> {
rLook.testLock();
});
threadB.setName("B");
threadA.start();
threadB.start();
}
}
输出结果基本就是相差 2 秒,即线程A获取锁后,线程B需要等线程A释放锁才能获取锁
线程A获取到锁1606723216664
线程B获取到锁1606723218664
2.3 读写锁
读写锁示例
public class ReadAndWriteTest {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private void writeLock(){
// 加锁
lock.writeLock().lock();
// 执行业务逻辑
System.out.println("线程"+Thread.currentThread().getName()+"获取到写锁"+System.currentTimeMillis());
// 睡眠,保证B线程进来时A线程还是获取锁状态
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 释放锁
lock.writeLock().unlock();
}
private void readLock(){
// 加锁
lock.readLock().lock();
// 执行业务逻辑
System.out.println("线程"+Thread.currentThread().getName()+"获取到读锁"+System.currentTimeMillis());
// 释放锁
lock.readLock().unlock();
}
public static void main(String[] args) {
// 线程1
ReadAndWriteTest rLook = new ReadAndWriteTest();
// 线程2
Thread threadA = new Thread(() -> {
rLook.writeLock();
});
threadA.setName("A");
Thread threadB = new Thread(() -> {
rLook.readLock();
});
threadB.setName("B");
threadA.start();
threadB.start();
}
}
输出结果如下,刚好相差2秒;当线程A获取到写锁,线程B必须等待线程A释放写锁后才能获取到读锁;
线程A获取到写锁1606724500217
线程B获取到读锁1606724502217
三 总结
通过本篇文章,我们大概知道常见的锁的使用方式;
- ReentrantLock 和 synchronized 都是 独占锁,又是可重入锁;独占锁想必大家都知道,重点解释下可重入锁,
当线程A获取到锁进入 methodA 时,再次调用 methodB 就不需要再次获取锁,即代表可重入锁;
class Test{
public synchronized void methodA() {
methodB();
}
public synchronized void methodB() {
}
}
-
synchronized不是可中断锁,而Lock是可中断锁。如果某一线程A正在执行锁中的代码,线程B由于等待时间过长去处理其它业务,就称线程B获得的锁为可中断锁;
-
公平锁尽量以请求锁的顺序来获取锁。比如有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这就是公平锁;非公平锁获取方式就是完全随机,抢占模式;
-
读写锁一般用于操作文件,只要涉及到写锁都是独占锁;读锁是共享锁;
有关 其它锁的知识,可以阅读知识追寻者以前发布的并发编程系列文章
欢迎关注我的公众号:知识追寻者,送原创PDF,面经,开源系统