感谢『石头StoneWang』对可重入锁的一句深入灵魂的总结:可重入就是说某个线程已经获得某个锁,可以再次获取这把锁而不会出现死锁。
1.可重入锁
- synchronized
- ReentrantLock
可重入锁的两个特性:
- 当一个线程 A获得锁后,那么线程A在没有释放当前锁时可以再次获得这个锁
- 而其他线程是不可以获得这个锁的。只有在这个线程 A 释放了当前锁后,其他线程才可以获得这个锁。
1.1 synchronized可重入锁的特性:
public class SynchronizedLockTest {
//类锁,实际是锁类的class对象
private static synchronized void synClass(){
Thread thread = Thread.currentThread();
System.out.println("当前执行synClass的线程名称:" + thread.getName());
System.out.println("synClass going...");
subSynClass();
System.out.println("synClass end");
}
private static synchronized void subSynClass(){
Thread thread = Thread.currentThread();
System.out.println("当前执行 subSynClass 的线程名称:" + thread.getName());
System.out.println("subSynClass going...");
SleepTools.second(3);
System.out.println("subSynClass end");
}
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("第一个线程开始执行");
synClass();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("第二个线程开始执行");
synClass();
}
});
thread.setName("第一个线程");
thread2.setName("第二个线程");
thread.start();
thread2.start();
}
}
输出结果:
第一个线程开始执行
第二个线程开始执行
当前执行synClass的线程名称:第一个线程
synClass going...
当前执行 subSynClass 的线程名称:第一个线程
subSynClass going...
subSynClass end
synClass end
当前执行synClass的线程名称:第二个线程
synClass going...
当前执行 subSynClass 的线程名称:第二个线程
subSynClass going...
subSynClass end
synClass end
可以看到这个示例中使用了 synchronized 类锁,这就意味着在这个类中通篇就只有一把锁。
- synchronized的可重入性的第一个特性就表现在: 当『第一个线程』执行加了synchronized 的SynClass()方法时拿到了锁,那么在SynClass()中依然可以调用加了synchronized的subSynClass()方法。
- synchronized的可重入性的第二个特性:当『第二个线程』,去调用synClass时。因为『第一个线程』还没有释放锁,所以『第二个线程』就会阻塞等待。
注:synchronized 当方法或代码块执行完毕才会释放锁
1.2 ReentrantLock可重入锁的特性:
public class ReentrantLockTest2 {
static Lock lock = new ReentrantLock();
public static void methodTest() {
System.out.println(Thread.currentThread().getName() + "进入方法等待.....");
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "methodTest do something start。。。。");
subMethodTest();
System.out.println(Thread.currentThread().getName() + "methodTest do something end。。。。。");
} finally {
lock.unlock();
}
}
public static void subMethodTest() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "subMethodTest do something start");
SleepTools.second(1);
System.out.println(Thread.currentThread().getName() + "subMethodTest do something end");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
methodTest();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
methodTest();
}
});
thread.setName("A线程");
thread2.setName("B线程");
thread.start();
thread2.start();
}
}
输出结果
A线程进入方法等待.....
B线程进入方法等待.....
A线程methodTest do something start。。。。
A线程subMethodTest do something start
A线程subMethodTest do something end
A线程methodTest do something end。。。。。
B线程methodTest do something start。。。。
B线程subMethodTest do something start
B线程subMethodTest do something end
B线程methodTest do something end。。。。。
- ReentrantLock 的可重入性的第一个特性就表现在: A 线程执行methodTest()拿到了 lock 锁,在调用subMethodTest()时,依然可以进入 被 lock 锁住的代码段。
- synchronized的可重入性的第二个特性:当 B 线程去调用methodTest()时,lock 锁住的代码块就进不去了,只有在外面等待。
ReentrantLock 和 synchronized 的不同在于,需要手动释放锁,所以使用 ReentrantLock的时候一定要手动释放锁,并且加锁次数和释放次数要一样。
- 如果把上面代码methodTest()方法中的 『lock.unlock();』 注掉,那么打印结果如下:
B线程进入方法等待.....
A线程进入方法等待.....
B线程methodTest do something start。。。。
B线程subMethodTest do something start
B线程subMethodTest do something end
B线程methodTest do something end。。。。。
B 线程 只释放了一次锁, A 线程就会永远阻塞,也拿不到执行的机会了。
1.3 可重入锁的实现
public class Lock{
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock()
throws InterruptedException{
Thread thread = Thread.currentThread();
while(isLocked && lockedBy != thread){
wait();
}
isLocked = true;
lockedCount++;
lockedBy = thread;
}
public synchronized void unlock(){
if(Thread.currentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
}
加锁时的逻辑:
- 加锁时,先获取当前线程。(识别谁需要锁)
Thread thread = Thread.currentThread();
- 判断:当临界资源已被锁上,但当前请求锁的线程又不是之前锁上临界资源的线程。那么当前请求锁的线程需要等待。
while(isLocked && lockedBy != thread){
wait();
}
可以顺利拿到锁,而不会 wait 的2种情况:
- 当前锁没有线程使用.
- 当前锁有线程使用,当前请求锁的线程就是现在正在使用锁的线程。
- 拿到锁后
isLocked = true; // 当前锁已经被使用了
lockedCount++;//锁的次数+1
lockedBy = thread;//记录拿到锁的线程
当前请求锁的线程先把锁加上,然后把上锁次数+1,然后把自己(本线程)赋值给lockedBy,以说明当前谁用了这把锁方便之后重入的时候做while判断。
解锁的逻辑:
- 首先看看要求解锁的线程是不是当前用锁的线程。不是则什么也不做。(当然不能随意让其他的线程一执行unlock代码就能解锁使用啊。那这样相当于谁都有一把钥匙了,这里这个判断也就是说明解锁的必须是加锁的)
- 那么把加锁次数减一。
- 然后在判断加锁次数有没有变为0。
- 变为0说明,这个锁已经完全解锁了。锁上标识islocked可以复位了。
并且随机唤醒某个被wait()等待的线程 : notify()
2.不可重入锁
既然同一个线程可多次进入同一把锁叫做可重入锁,那么不可重入锁就是同一个线程不可多次进入同一把锁。
public class NotReentrantLock{
private boolean isLocked = false;
public synchronized void lock() {
while(isLocked){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify();
}
public static void main(String[] args) {
NotReentrantLock lock = new NotReentrantLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
System.out.println("第1次获取锁,这个锁是:" + lock);
int index = 1;
while (true) {
try {
lock.lock();
System.out.println("第" + (++index) + "次获取锁,这个锁是:" + lock);
try {
Thread.sleep(new Random().nextInt(200));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (index == 10) {
break;
}
} finally {
lock.unlock();
}
}
} finally {
lock.unlock();
}
}
}).start();
}
}
输出:
第1次获取锁,这个锁是:com.xiangxue.可重入锁.NotReentrantLock@15c02541
可以看到 同一个线程 在 第二次获得 lock 锁的时候就阻塞了
参考:
https://blog.csdn.net/w8y56f/article/details/89554060
https://blog.csdn.net/qq_29519041/article/details/86583945