重入锁:
「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」。
重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,并且支持获取锁时的公平和非公平性选择
该特性的实现需要解决以下两个问题。
1)线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
2)锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放
ReentrantLock:显式的重进入,调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
synchronized:隐式的重进入,如一个synchronized修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁,而不像Mutex由于获取了锁,而在下一次获取锁时出现阻塞自己的情况。
公平锁与非公平锁:
非公平锁:线程饿死,但是效率高
公平锁:雨露均沾,效率相对较低
公平性与否是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO(先进先出)。
死锁:
之前的文章内容:Java多线程编程(同步、死锁、生产消费)
线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
产生死锁必须具备以下四个条件(操作系统部分):
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
解决方式:(破坏其中之一就好)
- 破坏互斥条件(无法破坏)
- 破坏请求与保持条件
- 破坏不剥夺条件
- 破坏循环等待条件
·避免一个线程同时获取多个锁。
·避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
·尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
实现代码:
package com.JUC;
import java.util.concurrent.TimeUnit;
public class deadlock05 {
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized(a){ //获取a资源,并加锁
System.out.println(Thread.currentThread().getName()+"持有a资源,试图获取b资源");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){ //获取b资源,并加锁
System.out.println(Thread.currentThread().getName()+"获取到b资源");
}
}
},"A线程").start();
new Thread(()->{
synchronized(b){ //获取b资源,并加锁
System.out.println(Thread.currentThread().getName()+"持有b资源,试图获取a资源");
synchronized (a){ //获取a资源,并加锁
System.out.println(Thread.currentThread().getName()+"获取到a资源");
}
}
},"B线程").start();
}
}
B线程持有b资源,试图获取a资源 A线程持有a资源,试图获取b资源
验证是否是死锁:
- jps 类似Linux ps -ef
- jstack JVM自带的堆栈跟踪工具
代码在运行的时候,进入命令行:
找到我们正在运行的程序pid.
观察下图:
最后显示发现一个死锁,然后上面的内容,
B线程当前锁的是....fb00,等待的是....faf0
A线程当前锁的是....faf0,等待的是....fb00;