什么是死锁,Java死锁详解
使用 synchronized 可以实现线程同步,这可以解决多线程并行访问数据带来的安全问题。但是任何事物都没有绝对的好与坏,synchronized 在解决线程安全问题的同时也会带来一个隐患,并且这个隐患是比较严重的,那就是死锁。
先来解释一下死锁的概念,举个生活中的例子,10 个人围一桌吃饭,但是每个人只有一根筷子,要求必须凑齐一双筷子才可以吃菜。也就是说每个人必须要拿到其他人的筷子,但是每个人又都不愿意把自己的筷子让给别人,都在等待其他人主动把筷子贡献出来。这样就形成了一个死局,如果一直保存这种状态,这个饭局就一直僵在这里,没有人可以吃到菜,这就是死锁。
如果把每个人看成一个线程,筷子就是线程要获取的资源,现在每个线程都占用一个资源并且不愿意释放,而且任意一个线程想继续执行就必须获取其他线程的资源,那么所有的线程都处于阻塞状态,程序无法向下执行也无法结束。
如何破解死锁呢?唯有某个线程愿意作出让步,贡献出自己的资源给其他线程使用。获取到资源的线程就可以执行自己的业务方法,执行完毕后会释放它所占用的两个资源,其他线程就可以依次获取资源来执行业务方法,问题就迎刃而解了。
相当于在那个僵持的饭局上,有一个人愿意作出牺牲,把自己的筷子让给身边的人,这样他身边的人就可以吃饭了,待他吃饱之后,会把他的筷子贡献出来,这样所有人都可以依次吃到饭了。
我们通过一段代码来演示死锁的情况:
图 1 发生死锁
张三获取了资源 chopsticks1,必须同时获取 chopsticks2 才能完成用餐,但是 chopsticks2 被李四占用,李四只有完成用餐才能释放 chopsticks2,但是李四完成用餐的必要条件是获取 chopsticks1,所以形成了一种互斥的关系,这就是死锁。
可以对代码进行修改,不要让两个线程并行访问,先启动代表张三的线程,休眠 2000ms,待张三完成用餐释放资源之后再启动代表李四的线程,修改后的代码为:
实际上死锁是一种错误,在实际开发中需要注意避免这种错误的出现。
先来解释一下死锁的概念,举个生活中的例子,10 个人围一桌吃饭,但是每个人只有一根筷子,要求必须凑齐一双筷子才可以吃菜。也就是说每个人必须要拿到其他人的筷子,但是每个人又都不愿意把自己的筷子让给别人,都在等待其他人主动把筷子贡献出来。这样就形成了一个死局,如果一直保存这种状态,这个饭局就一直僵在这里,没有人可以吃到菜,这就是死锁。
如果把每个人看成一个线程,筷子就是线程要获取的资源,现在每个线程都占用一个资源并且不愿意释放,而且任意一个线程想继续执行就必须获取其他线程的资源,那么所有的线程都处于阻塞状态,程序无法向下执行也无法结束。
如何破解死锁呢?唯有某个线程愿意作出让步,贡献出自己的资源给其他线程使用。获取到资源的线程就可以执行自己的业务方法,执行完毕后会释放它所占用的两个资源,其他线程就可以依次获取资源来执行业务方法,问题就迎刃而解了。
相当于在那个僵持的饭局上,有一个人愿意作出牺牲,把自己的筷子让给身边的人,这样他身边的人就可以吃饭了,待他吃饱之后,会把他的筷子贡献出来,这样所有人都可以依次吃到饭了。
我们通过一段代码来演示死锁的情况:
public class Chopsticks { } public class DeadLockRunnable implements Runnable{ public int num; private static Chopsticks chopsticks1 = new Chopsticks(); private static Chopsticks chopsticks2 = new Chopsticks(); @Override public void run() { // TODO Auto-generated method stub if(num == 1){ System.out.println(Thread.currentThread().getName()+"获取到chopsticks1,等待获取chopsticks2"); synchronized (chopsticks1) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (chopsticks2) { System.out.println(Thread.currentThread().getName()+"用餐完毕"); } } } if(num == 2){ System.out.println(Thread.currentThread().getName()+"获取到chopsticks2,等待获取chopsticks1"); synchronized (chopsticks2) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (chopsticks1) { System.out.println(Thread.currentThread().getName()+"用餐完毕"); } } } } } public class DeadLockTest { public static void main(String[] args) { DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable(); deadLockRunnable1.num = 1; DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable(); deadLockRunnable2.num = 2; new Thread(deadLockRunnable1,"张三").start(); new Thread(deadLockRunnable2,"李四").start(); } }运行程序,结果如下图所示:
图 1 发生死锁
张三获取了资源 chopsticks1,必须同时获取 chopsticks2 才能完成用餐,但是 chopsticks2 被李四占用,李四只有完成用餐才能释放 chopsticks2,但是李四完成用餐的必要条件是获取 chopsticks1,所以形成了一种互斥的关系,这就是死锁。
可以对代码进行修改,不要让两个线程并行访问,先启动代表张三的线程,休眠 2000ms,待张三完成用餐释放资源之后再启动代表李四的线程,修改后的代码为:
public class DeadLockTest { public static void main(String[] args) { DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable(); deadLockRunnable1.num = 1; DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable(); deadLockRunnable2.num = 2; new Thread(deadLockRunnable1,"张三").start(); try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) { } new Thread(deadLockRunnable2,"李四").start(); } }运行程序,结果为:
张三获取到chopsticks1,等待获取chopsticks2
张三用餐完毕
李四获取到chopsticks2,等待获取chopsticks1
李四用餐完毕
实际上死锁是一种错误,在实际开发中需要注意避免这种错误的出现。