Java从入门到放弃32—死锁/ReentrantLock类(可重入锁)/Lock和Synchronized的区别
01 死锁
- 多个线程各自占有一些共享资源 , 并且互相等待其他线程占有的资源才能运行 , 而
导致两个或者多个线程都在等待对方释放资源 , 都停止执行的情形 。某一个同步块
同时拥有 “ 两个以上对象的锁 ” 时 , 就可能会发生 “ 死锁 ” 的问题 。 - 产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系。
- 解决方案:只要打破上述四个条件的任意一个条件就可以避免死锁发生
- 程序示例:
//日常生活中的化妆为例,化妆需要口红和梳子才能完成。设定口红和梳子各只有一份,有两个人分别先拿到口红和梳子,双方需要互相等待对方释放资源才能完成化妆,这就产生了死锁。
public class DeadLock {
public static void main(String[] args) {
Makeup makeup = new Makeup(0);
Makeup makeup1 = new Makeup(1);
new Thread(makeup,"A").start();
new Thread(makeup1,"B").start();
}
}
class Makeup implements Runnable{
//化妆需要口红和梳子
//将梳子和口红定义为一个资源
static Comb comb=new Comb();
static Lipstick lipstick=new Lipstick();
int choice;
//提供有参构造,保证两个人最初的选择不同
public Makeup(int choice) {
this.choice = choice;
}
@Override
public void run() {
if (choice==0){
//先拿梳子再拿口红
synchronized (comb){
System.out.println(Thread.currentThread().getName()+" get the comb");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//发生死锁现象:锁嵌套锁
// synchronized (lipstick){
// try {
// Thread.currentThread().sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"get the lipstick");
// System.out.println(Thread.currentThread().getName()+"完成化妆");
// }
}
//解决死锁方式:释放锁
synchronized (lipstick){
//等待释放锁
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" get the lipstick");
System.out.println(Thread.currentThread().getName()+"完成化妆");
}
}else{
//先拿口红再拿梳子
synchronized (lipstick){
System.out.println(Thread.currentThread().getName()+" get the lipstick");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//发生死锁现象:锁嵌套锁
// synchronized (comb){
// try {
// Thread.currentThread().sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"get the comb");
// System.out.println(Thread.currentThread().getName()+"完成化妆");
// }
}
//解决死锁方式:不要嵌套锁
synchronized (comb){
//等待释放锁
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" get the comb");
System.out.println(Thread.currentThread().getName()+"完成化妆");
}
}
}
}
class Lipstick{
//口红
}
class Comb{
//梳子
}
- 运行结果
02 ReentrantLock 可重入锁
-
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。使用Lock对象充当同步锁。
-
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
-
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
-
class A{ private final ReentrantLock lock = new ReenTrantLock(); public void method(){ lock.lock(); try{ //保证线程安全的代码; } finally{ lock.unlock(); //如果同步代码有异常,要将unlock()写入finally语句块 } } }
-
注:把解锁操作括在finally子句之内是至关重要的。如果在临界区的代码抛出异常,锁必须被释放。否则,其他线程将永远阻塞。
03 Lock和Synchronized的区别
- Lock和Synchronized的区别
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:
- Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)