死锁
为了避免多个线程同时访问同一个数据而产生不可预料的结果,需要将各个线程对同一数据的访问同步。同步最常见的方法就是用锁。而用锁的过程中,两个或者多个线程已经拥有资源,而且都在等待对放线程释放资源,都处于等待状态,可能发生死锁的问题。
比如下面这种情况,程序一直无法结束,进程一直处于等待状态。
public class DeadLock {
private static final String WATER = "water";
private static final String FOOD = "food";
public static void main(String[] args) {
Thread cat = new Thread(() -> {
getWaterFirst();
}, "小猫");
Thread rabbit = new Thread(() -> {
getFoodFirst();
}, "小兔子");
cat.start();
rabbit.start();
}
public static void getWaterFirst() {
synchronized (WATER) {
System.out.println(Thread.currentThread().getName() + "拿到water");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
getFoodFirst();
}
}
public static void getFoodFirst() {
synchronized (FOOD) {
System.out.println(Thread.currentThread().getName() + "拿到food");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
getWaterFirst();
}
}
}
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用;
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放开
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;
只要破坏其中的任意一个条件都可以避免死锁发生。
LOCK锁
使用格式如下
public class LockTest {
private final ReentrantLock lock=new ReentrantLock();
public void test() {
lock.lock();
try {
}finally {
//关锁
lock.unlock();
}
}
}
Lock和synchronized有以下几点不同:
1)Lock是一个接口,是显示锁(手动开启和关闭锁),而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是隐式锁,出了作用域自动释放。
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。