在多线程运行中,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();

}


运行结果:

Java由于资源竞争实现死锁_锁机制

发生死锁

运行过程如下:

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();
}


重新执行:

Java由于资源竞争实现死锁_锁机制_02

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

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

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