Java因为资源竞争实现死锁

2021年11月20日 阅读数:7
这篇文章主要向大家介绍Java因为资源竞争实现死锁,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

在多线程运行中,Java对于一个对象中的共享资源提供了可重入锁机制,容许在使用共享资源或代码时加锁、使用完毕解锁为代码段赋予原子性。
下面经过产生死锁的例子,来分析这个机制:java

public class MethodBlock {
    private ReentrantLock lock1 = new ReentrantLock();
    private ReentrantLock lock2 = new ReentrantLock();

    public void method1(String name) throws InterruptedException {
        lock1.lock();
        System.out.println("这里是方法1 " + name);
        Thread.sleep(500);
        System.out.println("方法1结束调用方法2 " + name);
        method2(name);
        lock1.unlock();
    }

    public void method2(String name) throws InterruptedException {
        lock2.lock();
        System.out.println("这里是方法2 " + name);
        Thread.sleep(500);
        method1(name);
        System.out.println("方法2结束调用方法1 " + name);
        lock2.unlock();
    }
}

    public static void main(String[] args) throws InterruptedException {
        MethodBlock obj1 = new MethodBlock();
//        MethodBlock obj2 = new MethodBlock();
        //测试死锁
        Runnable r1 = () -> {
            try {
                obj1.method1("t1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Runnable r2 = () -> {
            try {
                obj1.method2("t2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        
    }

运行结果:

发生死锁多线程

运行过程以下:

1.线程t1启动,调用start()方法进入可运行状态并运行
2.t1执行method1方法并获得可重入锁lock1,执行到sleep(500)时进入阻塞态持续500ms,此时cpu空闲。
3.线程t2启动,运行,执行method2方法并获得可重入锁lock2,一样执行到sleep(500)时进入阻塞态持续500ms。
4.t1阻塞结束从新执行,但愿调用method2方法,但此时method2方法被线程t2加上了lock2锁,所以t1阻塞。
5.t2阻塞结束从新执行,但愿调用method1方法,但此时method1方法被线程t1加上了lock1锁,t2也进入了阻塞态。
此时两个线程互相等待,且占有资源不可剥夺。出现死锁。
所以能够看出,锁是对于一个对象的共享资源来讲的,多个线程访问这些资源时,会等待有锁的进程释放锁才能够访问。测试

什么是可重入锁?

持有该锁的线程能够重复地得到已经持有的锁。锁保持一个持有技术来跟踪对lock方法的嵌套调用。线程在每一次调用lock都要调用unlock来释放锁。因为这一特性,被一个锁保护的代码可继续访问另外一个使用相同锁的方法。
好比,将MethodBlock类作以下修改:线程

private ReentrantLock lock1 = new ReentrantLock(); //全部方法所有使用同一个锁
//    private ReentrantLock lock2 = new ReentrantLock();

    public void method1(String name) throws InterruptedException {
        lock1.lock();
        System.out.println("这里是方法1 " + name);
        Thread.sleep(2000);
        System.out.println("方法1结束调用方法2 " + name);
        method2(name);
        lock1.unlock();
    }

    public void method2(String name) throws InterruptedException {
        lock1.lock();
        System.out.println("这里是方法2 " + name);
        Thread.sleep(500);
        method1(name);
        System.out.println("方法2结束调用方法1 " + name);
        lock1.unlock();
    }

从新执行:

能够看到,一直都是线程t1在执行,这是由于只有一个锁lock1,t1执行方法method1(..)得到锁以后,t2想要访问代码只能阻塞等待t1释放锁lock1。然而,t1在得到锁以后能够继续调用使用相同锁的method2(..)方法。code

PS:因为run中产生未捕获的异常也会致使线程结束,若该线程还持有锁,则这个锁将永远不会释放,所以最好将代码写成以下形式:

lockObj.lock()
try
{
  do something..
}
finally
{
  lockObj.unlock(); //锁最后必定会被释放
}