什么是可重入锁?
可重入锁 又叫 递归锁,指当一个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
什么意思呢?
同一个线程外层函数获得锁之后,内层递归函数也想要获得该锁,那么这个线程可以重复的获取该锁,即上了多把锁。
可重入锁的意义:
同一个线程多次获取锁,而不会出现死锁。
非可重入锁会发生什么情况呢?
当一个线程已经获取了一个锁,那么其内层函数如果也需要获取该锁,就必须等待这个线程释放该锁;但这个线程现在又处于等待该锁又不会释放锁的状态,因此会出现死锁。
而可重入锁,不需要等待自身的线程释放该锁才能再次获取,而是可以在不释放该锁的情况下再次加一把锁。从而避免了死锁的发生。
Synchronized & ReentrantLock
Synchronized 和 ReentrantLock都是可重入锁;Synchronized 是自动获取锁自动释放锁,而ReentrantLock需要手动加锁手动解锁,需要注意,加几层锁就需要释放几层锁。如果加锁多于解锁,程序运行不会报错,但会一直运行不会结束,可能会导致死锁。如果解锁多于加锁,运行时会报错IllegalMonitorStateException。
synchronized案例
public class SynchronizedDemo {
public static void main(String[] args) {
Phone phone=new Phone();
syncTest(phone);
System.out.println();
}
private static void syncTest(Phone phone) {
new Thread(()->{
try{
phone.sendSMS();
}catch (Exception e){
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try{
phone.sendSMS();
}catch (Exception e){
e.printStackTrace();
}
},"t2").start();
}
}
class Phone implements Runnable{
//Synchronized TEST
public synchronized void sendSMS(){
System.out.println(Thread.currentThread().getId()+"\t"+Thread.currentThread().getName()+"\t"+"sendSMS()");
sendEmail();//递归调用
}
public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getId()+"\t"+Thread.currentThread().getName()+"\t"+"sendEmail()");
}
}
运行结果:
t1,t2两个线程都想去获取对象锁phone,当一个t1线程调用了sendSMS()方法先获得锁时,再调用sendEmail()方法,sendEmail()也需要synchronized加锁,由结果可见再次获取锁成功;自动释放锁后,最后再由t2线程获取锁。
由打印结果可知,synchronized是一个可重入锁,每个线程都获取了两次锁。
ReentrantLock案例
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone=new Phone();
Thread t3=new Thread(phone,"t3");
Thread t4=new Thread(phone,"t4");
t3.start();
t4.start();
}
}
class Phone implements Runnable{
//Reentrant TEST
Lock lock=new ReentrantLock();
@Override
public void run() {
get();
}
public void get(){
lock.lock();
try{
System.out.println(Thread.currentThread().getId()+"\t"+Thread.currentThread().getName()+"\t"+"get()");
set();
}finally {
lock.unlock();
}
}
public void set(){
lock.lock();
try{
System.out.println(Thread.currentThread().getId()+"\t"+Thread.currentThread().getName()+"\t"+"set()");
}finally {
lock.unlock();
}
}
}
运行结果如下:
同样,创建t3,t4两个线程,调用get()方法,t3线程先获得锁,然后再调用set()方法再次获得锁,再经过两次手动释放锁后,t4线程再获得锁。
由结果可知,ReentrantLock也是一个可重入锁,一个线程可多次获得相同的锁。