b2科目四模拟试题多少题驾考考爆了怎么补救
b2科目四模拟试题多少题 驾考考爆了怎么补救

什么状况下Java程序会造成死锁?如何定位、修复?

电脑杂谈  发布时间:2019-09-01 15:04:17  来源:网络整理

c++线程死锁_多线程死锁的伪代码_线程 死锁

初步知道:

死锁是一种特定的程序状况,在实体之间,由于循环依赖造成相互一直进入期待之中线程死锁的伪代码,没有任何个体可以再次前进。死锁不仅仅是程之间会出现,存在资源独占的进程之间相同也或许发生死锁。通常来说,大多都是聚集在多线程场景中的死锁,指两个或多个线程之间,由于彼此有别人必须的锁,而永久进入阻塞状况。

下图理解基本的死锁:

在这里插入图片描述

定位死锁最常用的方法就是利用jstack等软件获取线程栈,然后定位互相之间的依赖关系,进而找到死锁。如果是非常明显的死锁,往往jstack等能够直接定位,类似JConsole甚至可以在图形界面进行有限的死锁检测。

如果程序运行时出现了死锁,绝大多数情况下都是无法解决的,只能重启、修正程序原本问题。所以代码开发阶段相互审查,或者借助软件进行预防性排查,往往只是很重要的。

针对死锁,还可以延伸出许多其它的问题:

知识扩展

分析开始之前,先以一个基本的死锁程序为例,只是用了两个嵌套的synchronized去获取锁,如下:

c++线程死锁_线程 死锁_多线程死锁的伪代码

	public class DeadLockSample extends Thread {
		private String first;
		private String second;
		public DeadLockSample(String name, String first, String second) {
			super(name);
			this.first = first;
			this.second = second;
	}
	public void run() {
		synchronized (frs) {
			Sysem.out.println(this.getName() + " obtained: " + frs);
		try {
			Thread.sleep(1000L);
			synchronized (second) {
				Sysem.out.println(this.getName() + " obtained: " + second);
			}
		} catch (InterruptedException e) {
			// Do nothing
		}
	}
}
public satic void main(String[] args) throws InterruptedException {
	String lockA = "lockA";
	String lockB = "lockB";
	DeadLockSample t1 = new DeadLockSample("Thread1", lockA, lockB);
	DeadLockSample t2 = new DeadLockSample("Thread2", lockB, lockA);
	t1.sart();
	t2.sart();
	t1.join();
	t2.join();
	}
}

这个程序编译执行后,几乎每天都可以重现死锁。另外,这里有个非常有意思的地方,为什么我先调用Thread1的start,但是Thread2却先打印下来了呢?这就是因为线程调度依赖于(操作系统)调度器,虽然你可以借助优先级之类进行制约,但是详细状况是不确定的。

下面来模拟问题定位,我就选择最常用的jstack,其他一些类似JConsole等图形化的软件,请自行查找。

首先,可以使用jps或者平台的ps命令、任务管理器等软件,确定进程ID。

其次,调用jstack获取线程栈:

		${JAVA_HOME}\bin\jstack your_pid

然后预测得出,具体片段如下:

在这里插入图片描述

最后,结合代码剖析线程栈信息。上面这个输出比较显著,找到进入BLOCKED状态的线程,按照试图获得( waiting)的锁ID(请看我标记为相似颜色的数字)查找,很快就定位问题。 jstack本身也会把类似的简单死锁抽取出来,直接打印出来。

线程 死锁_c++线程死锁_多线程死锁的伪代码

在实际应用中,类死锁情况或许有这么清晰的输出,但是总体上可以理解为:

区分线程状态 -> 查看等待目标 -> 对比Monitor等持有状态

所以,理解线程基本状况和并发相关元素是定位问题的关键,然后配合程序调用栈结构,基本就可以定位到详细的问题代码。

如果我们是开发自己的管理软件,需要用非常程序化的方法扫描服务进程、定位死锁,可以考量使用Java提供的标准管理API, ,其直接就提供

了findDeadlockedThreads()方法用于定位。为便于说明,我设置了DeadLockSample,请看上面的代码片段。

public satic void main(String[] args) throws InterruptedException {
	ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
	Runnable dlCheck = new Runnable() {
		@Override
		public void run() {
			long[] threadIds = mbean.findDeadlockedThreads();
			if (threadIds != null) {
					ThreadInfo[] threadInfos = mbean.getThreadInfo(threadIds);
					System.out.println("Detected deadlock threads:");
				for (ThreadInfo threadInfo : threadInfos) {
					System.out.println(threadInfo.getThreadName());
				}
			}
		}
	}
};
	ScheduledExecutorService scheduler =Executors.newScheduledThreadPool(1);
	// 稍等5秒,然后每10秒进行一次死锁扫描
	scheduler.scheduleAtFixedRate(dlCheck, 5L, 10L, TimeUnit.SECONDS);
// 死锁样例代码…
}

重新编译执行,你能够看见死锁被定位到的输出。在实际应用中,就可以据此收集进一步的信息,然后进行预警等后续处理。但是要切记的是,对线程进行快照本身是一个相对重量级的操作,还是要慎重选择频度和时机。

如何在编程中尽量防止死锁呢?

首先,我们来小结一下前面举例中死锁的形成包括那些基本元素。基本上死锁的出现是因为:

c++线程死锁_线程 死锁_多线程死锁的伪代码

所以,我们可以据此预测可能的防止死锁的模式和技巧。

第一种方法

如果可能的话,尽量减少使用多个锁,并且唯有需要时才持有锁。否则,即使是比较精通并发编程的工程师,也常常会掉入坑里,嵌套的synchronized或者lock非常容易出问题。

Java NIO的谋求代码向来以锁多著称,一个原因是,其本来模型就比较复杂,某种程度上是不得不如此;另外是在设计时,考虑到既要支持阻塞方式,又要支持非阻塞模式。直接结果就是,一些基本操作如connect,需要操作三个锁以上,在今天的一个JDK改进中,就出现了死锁现象。

将其简化为以下的伪代码,问题是暴露在HTTP/2客户端中,这是个比较现代的反应式风格的API,非常推荐学习使用。

/// Thread HttpClient-6-SelectorManager:
readLock.lock();
writeLock.lock();
// 持有readLock/writeLock,调用close()需要获得closeLock
close();
// Thread HttpClient-6-Worker-2 持有closeLock
implCloseSelectableChannel (); //想获得readLock

在close发生时多线程死锁的伪代码, HttpClient-6-SelectorManager线程持有readLock/writeLock,试图获取closeLock;与此同时,另一个HttpClient-6-Worker-2线程,持有closeLock,试图获取readLock,这就不可避免地处于了死锁。

这里非常难懂的地方在于, closeLock的持有状态(就是我标记为红色的个别) 并没有程栈中显示起来,请参考我在图示中标记的个别。

在这里插入图片描述

c++线程死锁_多线程死锁的伪代码_线程 死锁

所以,从程序设计的视角审视,如果我们带给一段程序很多的职责,出现“既要…又要…”的状况时,可能就必须我们思考下设计模式或目的是否合理了。对于类库,因为其基础、共享的定位,比应用研发通常非常令人担忧,需要认真权衡之间的平衡。

第二种方法

如果需要使用多个锁,尽量设计好锁的获取顺序,这个说起来简单,做出来可不容易,你可以参看著名的银行家算法。

一般的状况,我建议可以采取些简单的辅助方式,比如:

** 第三种方法**

使用带超时的方式,为程序带给更多可控性。

类似Object.wait(…)或者CountDownLatch.await(…),都支持所谓的timed_wait,我们完全可以就不假设该锁一定会获取,指定超时时间,并为能够受到锁时打算退出逻辑。

并发Lock实现,如ReentrantLock还支持非阻塞式的获得锁操作tryLock(),这是一个插队行为( barging),并不在乎等待的公平性,如果执行时对象正好没有被独占,则直接获得锁。有时,我们期望条件允许就尝试插队,不然就根据现有公平性规则等待,一般选用以下的方式:

if (lock.tryLock() || lock.tryLock(timeout, unit)) {
		// ...
	}

第四种方法

业界也是一些其它方面的尝试,比如借助静态代码分析(如FindBugs)去查找固定的方式,进而定位也许的死锁或者竞争状况。实践证明这些方式也是必定作用,请参考相关文档。

除了典型应用中的死锁场景,其实也有一些更倍感苦恼的死锁,比如类加载过程出现的死锁,尤其是在框架大量使用自定义类加载时,因为通常不是在应用本来的代码库中, jstack等软件也不见得能够显示全部锁信息,所以处理出来非常棘手。对此, Java有官网文档进行了具体解释,并对于特定状况提供了相应JVM参数和基本原则。


本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-121333-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      每日福利
      热点图片
      拼命载入中...