Java死锁问题

本文介绍了Java中的死锁现象,包括进程和线程死锁的定义,阐述了产生死锁的四个必要条件,并通过代码实例展示了死锁的效果。同时,提出了避免死锁的策略,如指定加锁顺序、设置加锁时限以及破坏环路等待条件等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

死锁定义

1)进程死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

2)线程死锁是指由于两个或者两个以上的线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

产生死锁的四个必要条件

1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程(或线程,以下同)占用。如果此时还有其它进程(线程)请求资源,则请求者只能等待,直至占有资源的进程(线程)用毕释放。如:当线程A进入synchronized,持有一个对象(Monitor)后,其他线程不能再持有这个对象了。

2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

死锁-Java代码实例:

public class DeadLock {

	public final static Object A = new Object();
	public final static Object B = new Object();
    public final static Object C = new Object();

	public static void main(String[] args) throws InterruptedException {

		Thread thread01 = new Thread(() -> {
				synchronized (A) {//线程thrad-1持有A对象
					System.out.println(Thread.currentThread().getName()+" lock A");
					try {
						Thread.sleep(2000L);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					synchronized (B) {//保持持有A对象,请求持有B对象
						System.out.println(Thread.currentThread().getName()+" lock B");
					}
				}
		},"thrad-1");
	
		Thread thread02 = new Thread(() -> {
				synchronized (B) {
					System.out.println(Thread.currentThread().getName()+" lock B");
					try {
						Thread.sleep(2000L);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					synchronized (A) {//synchronized (C){打印对象C}
						System.out.println(Thread.currentThread().getName()+" lock A");
					}
				}
		},"thrad-2");

		thread01.start();//执行线程thrad-1
		//thread01.join();//阻塞,等待线程thrad-1执行完,再执行后面的代码逻辑。即:先让线程线程thrad-1执行完
		thread02.start();//执行线程thrad-2
	}

}

死锁的效果:

Java程序不结束,满足死锁4个条件,形成死锁;

避免死锁的方法

只要破坏产生死锁的死锁的4个必要条件中的一个,即可解决死锁问题;

1)破坏互斥条件不剥夺条件,对sychronized来说,是JVM底层设定的,每个对象都有一个monitor(监视器),在同步操作中,一个线程(或进程)持有一个对象的monitor后,有monitorenter操作,同步方法结束或异常时,有monitorexit操作。所以我们是破坏不了这个2个条件的;如果不是sychronized,如通过Lock完成的互斥条件不剥夺条件,就可以更改;

2)破坏请求和保持条件,常见的操作有:加锁顺序(指定加锁的顺序或者线程执行的顺序)、加锁时限(对加锁的对象增加超时限制,避免一直占用);

3)破坏环路等待条件死锁检测:多线程对期望持有的锁对象不能形成相互(环状)依赖的情况。

如,上述死锁代码中;线程01持有对象A后,去请求持有对象B;

                                    线程02持有对象B后,去请求持有对象A,-------多线程持有的对象A、B是相互依赖的;造成死锁;

     如果是这样,线程01持有对象A后,去请求持有对象B;

                           线程02持有对象B后,去请求持有对象C,  ------------A、B、C之间没有相互依赖,不会死锁;

死锁是指两个或以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。在Java程序中,当线程需要同步访问共享资源时就可能发生死锁。 以下是导致死锁的四个必要条件: 1. **互斥条件**:一个资源每次只能被一个线程使用; 2. **请求与保持条件**:一个已经持有一个资源的线程可以申请新的资源,在获得之前不会释放已有的资源; 3. **不可剥夺条件**:已经被分配给某一线程的资源不能再强行收回; 4. **循环等待条件**:若干个线程形成了首尾相接的一个环形链路,每个线程都在等待下一个线程所占有的资源; 为了演示如何发生死锁的情况,我们来看一个小例子: ```java public class DeadlockExample { private final Object lockA = new Object(); private final Object lockB = new Object(); public void methodOne() { synchronized (lockA) { // 模拟一些操作... try { Thread.sleep(50); } catch (InterruptedException e) {} synchronized (lockB) { System.out.println("method one"); } } } public void methodTwo() { synchronized (lockB) { // 模拟一些操作... try { Thread.sleep(50); } catch (InterruptedException e) {} synchronized (lockA) { System.out.println("method two"); } } } public static void main(String[] args) throws InterruptedException{ DeadlockExample demo = new DeadlockExample(); Runnable r1 = () -> demo.methodOne(); Runnable r2 = () -> demo.methodTwo(); Thread thread1 = new Thread(r1); Thread thread2 = new Thread(r2); thread1.start(); thread2.start(); thread1.join(); thread2.join(); } } ``` 上述代码中`methodOne()` 和 `methodTwo()` 都会尝试获取对`lockA`和`lockB`这两个对象监视器的所有权。如果运气不好,可能导致其中一个方法先拿到了`lockA`, 另一个则拿到`lockB`. 这样就会形成相互之间的依赖, 最终进入一种僵局状态 - 死锁. 解决这个问题的办法有很多, 例如尽量减少锁的数量、按一定的顺序加锁等措施都可以有效地预防死锁的发生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值