Java多线程7:死锁

前言

死锁单独写一篇文章是因为这是一个很严重的、必须要引起重视的问题。这不是夸大死锁的风险,尽管锁被持有的时间通常很短,但是作为商业产品的应用程序每天可能要执行数十亿次获取锁->释放锁的操作,只要在这数十亿次操作中只要有一次发生了错误,就可能导致程序中发生死锁,并且即使通过压力测试也不可能找出所有潜在的死锁。

死锁

一个经典的多线程问题。

当一个线程永远地持有一个锁,并且其他线程都尝试去获得这个锁时,那么它们将永远被阻塞,这个我们都知道。如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。

在数据库系统的设计中考虑了监测死锁以及从死锁中恢复,数据库如果监测到了一组事物发生了死锁时,将选择一个牺牲者并放弃这个事物。Java虚拟机解决死锁问题方面并没有数据库这么强大,当一组Java线程发生死锁时,这两个线程就永远不能再使用了,并且由于两个线程分别持有了两个锁,那么这两段同步部分的代码都无法再运行了----除非终止并重启应用。

死锁是设计的BUG,问题比较隐晦。不过死锁造成的影响很少会立即显现出来,一个类可能发生死锁,并不意味着每次都会发生死锁,这只是表示有可能。当死锁出现时,往往是在最糟糕的情况----高负载的情况下

下面给出一个产生死锁的简单代码并且演示如何分析这是一个死锁:

public class DeadLock
{
    private final Object left = new Object();
    private final Object right = new Object();

    public void leftRight() throws Exception
    {
        synchronized (left)
        {
            Thread.sleep(2000);
            synchronized (right)
            {
                System.out.println("leftRight end!");
            }
        }
    }

    public void rightLeft() throws Exception
    {
        synchronized (right)
        {
            Thread.sleep(2000);
            synchronized (left)
            {
                System.out.println("rightLeft end!");
            }
        }
    }
}

注意这里一定要有"Thread.sleep(2000)"让线程睡一觉,不然一个线程运行了,另一个线程还没有运行,先运行的线程很有可能就已经连续获得两个锁了。写两个线程分别调用它们:

public class Thread0 extends Thread
{
    private DeadLock dl;

    public Thread0(DeadLock dl)
    {
        this.dl = dl;
    }

    public void run()
    {
        try
        {
            dl.leftRight();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
public class Thread0 extends Thread
{
    private DeadLock dl;

    public Thread0(DeadLock dl)
    {
        this.dl = dl;
    }

    public void run()
    {
        try
        {
            dl.leftRight();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

写个main函数调用一下:

public static void main(String[] args)
{
    DeadLock dl = new DeadLock();
    Thread0 t0 = new Thread0(dl);
    Thread1 t1 = new Thread1(dl);
    t0.start();
    t1.start();
}

至于结果,没有结果,什么语句都不会打印,因为死锁了。下面演示一下如何定位死锁问题:

1、jps获得当前Java虚拟机进程的pid

2、jstack打印堆栈,jstack打印内容的最后其实已经报告了发现了一个死锁,别管它

Thread-1处于BLOCKED状态,Thread-0处于BLOCKED状态。对这两个线程分析一下:

(1)Thread-1获得了锁0x000000003416a4e8,在等待锁0x000000003416a4d8

(2)Thread-0获得了锁0x000000003416a4d8,在等待锁0x000000003416a4e8

由于两个线程都在等待获取对方持有的锁,所以就这么永久等待下去了。

3、注意一下使用Eclipse/MyEclipse,这段程序如果不点击控制台上面的红色方框去Terminate掉它,而是右键->Run As->1 Java Application的话,这个进程会一直存在的,这时候可以利用taskkill命令去终止没有被Terminate的进程:

避免死锁的方式

既然可能产生死锁,那么接下来,讲一下如何避免死锁。

1、让程序每次至多只能获得一个锁。当然,在多线程环境下,这种情况通常并不现实

2、设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量

3、既然死锁的产生是两个线程无限等待对方持有的锁,那么只要等待时间有个上限不就好了。当然synchronized不具备这个功能,但是我们可以使用Lock类中的tryLock方法去尝试获取锁,这个方法可以指定一个超时时限,在等待超过该时限之后变回返回一个失败信息

时间: 2024-10-23 17:15:12

Java多线程7:死锁的相关文章

Java多线程之死锁与线程间通信简单案例

死锁定义 死锁是指两个或者多个线程被永久阻塞的一种局面,产生的前提是要有两个或两个以上的线程,并且来操作两个或者多个以上的共同资源:我的理解是用两个线程来举例,现有线程A和B同时操作两个共同资源a和b,A操作a的时候上锁LockA,继续执行的时候,A还需要LockB进行下面的操作,这个时候b资源在被B线程操作,刚好被上了锁LockB,假如此时线程B刚好释放了LockB则没有问题,但没有释放LockB锁的时候,线程A和B形成了对LockB锁资源的争夺,从而造成阻塞,形成死锁:具体其死锁代码如下:

java多线程中死锁情况的一个示例

下面是死锁情况的一个示例代码 package com.qust.demo.money; class A { public synchronized void foo(B b) { System.out.println(Thread.currentThread().getName() + " 进入A的foo"); try { Thread.sleep(200); } catch (InterruptedException ex) { ex.printStackTrace(); } Sys

Java多线程和死锁

一 .多线程: 售票窗口简单实例: public class SaleTicket { public static class Sale implements Runnable{ private int ticket=10; // @Override // public void run() { // while(true){ // synchronized(this){ //同步代码块 synchronized(this)锁着的是对象 // if(ticket>0){ // try { //

Java多线程:死锁

周末看到一个用jstack查看死锁的例子.昨天晚上总结了一下jstack(查看线程).jmap(查看内存)和jstat(性能分析)命令.供大家参考 1.Jstack 1.1   jstack能得到运行java程序的java stack和native stack的信息.可以轻松得知当前线程的运行情况.如下图所示  注:这个和thread dump是同样的结果.但是thread dump是用kill -3 pid命令,还是服务器上面少用kill为妙 1.2   命名行格式 jstack [ opti

Java多线程产生死锁的一个简单案例

synchronized是Java中的关键字,是一种同步锁.它修饰的对象有以下几种: 1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象:2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象:3. 修改一个静态方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象:4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对

java多线程(死锁,lock接口,等待唤醒机制)

一.Lock接口 常用方法 Lock提供了一个更加面对对象的锁,在该锁中提供了更多的操作锁的功能. 使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,对电影院卖票案例中Ticket类进行如下代码修改: public class Ticket implements Runnable { //共100票 int ticket = 100; //创建Lock锁对象 Lock ck = new ReentrantLock(); @Override public void run

java多线程_死锁的例子

/*说明:零长度的byte数组对象创建起来将比任何对象都经济,查看编译后的字节码: 生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码.*/ public class DeadLock { public static void main(String[] args) { byte[] lock1 = new byte[0]; byte[] lock2 = new byte[0]; Thread th1=new Thread(new

Java多线程:生产者消费者更佳的解决方法(确定不会出现死锁)

今天看了一片博文,讲Java多线程之线程的协作,其中作者用程序实例说明了生产者和消费者问题,但我及其他读者发现程序多跑几次还是会出现死锁,百度搜了下大都数的例子也都存在bug,经过仔细研究发现其中的问题,并解决了,感觉有意义贴出来分享下. 下面首先贴出的是有bug的代码,一个4个类,Plate.java: package CreatorAndConsumer; import java.util.ArrayList; import java.util.List; /** * 盘子,表示共享的资源

java多线程13 : 死锁

前言 死锁单独写一篇文章是因为这是一个很严重的.必须要引起重视的问题.这不是夸大死锁的风险,尽管锁被持有的时间通常很短,但是作为商业产品的应用程序每天可能要执行数十亿次获取锁->释放锁的操作,只要在这数十亿次操作中只要有一次发生了错误,就可能导致程序中发生死锁,并且即使通过压力测试也不可能找出所有潜在的死锁. 死锁 一个经典的多线程问题. 当一个线程永远地持有一个锁,并且其他线程都尝试去获得这个锁时,那么它们将永远被阻塞,这个我们都知道.如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得