Java死锁及解决
Java线程死锁如何避免这一悲剧 Java线程死锁需要如何解决,这个问题一直在我们不断的使用中需要只有不断的关键。不幸的是,使用上锁会带来其他问题。让我们来看一些常见问题以及相应的解决方法:
Java线程死锁
Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。假设有两个线程,分别代表两个饥饿的人,他们必须共享刀叉并轮流吃饭。他们都需要获得两个锁:共享刀和共享叉的锁。
假如线程 “A”获得了刀,而线程“B”获得了叉。线程“A”就会进入阻塞状态来等待获得叉,而线程“B”则阻塞来等待“A”所拥有的刀。这只是人为设计的例子,但尽管在运行时很难探测到,这类情况却时常发生。虽然要探测或推敲各种情况是非常困难的,但只要按照下面几条规则去设计系统,就能够避免Java线程死锁问题:
让所有的线程按照同样的顺序获得一组锁。这种方法消除了 X 和 Y 的拥有者分别等待对方的资源的问题。
将多个锁组成一组并放到同一个锁下。前面Java线程死锁的例子中,可以创建一个银器对象的锁。于是在获得刀或叉之前都必须获得这个银器的锁。
将那些不会阻塞的可获得资源用变量标志出来。当某个线程获得银器对象的锁时,就可以通过检查变量来判断是否整个银器集合中的对象锁都可获得。如果是,它就可以获得相关的锁,否则,就要释放掉银器这个锁并稍后再尝试。
最重要的是,在编写代码前认真仔细地设计整个系统。多线程是困难的,在开始编程之前详细设计系统能够帮助你避免难以发现Java线程死锁的问题。
Volatile 变量,volatile 关键字是 Java 语言为优化编译器设计的。以下面的代码为例:
1.class VolatileTest {
2.public void foo() {
3.boolean flag = false;
4.if(flag) {
5.//this could happen
6.}
7.}
8.}
一个优化的编译器可能会判断出if部分的语句永远不会被执行,就根本不会编译这部分的代码。如果这个类被多线程访问, flag被前面某个线程设置之后,在它被if语句测试之前,可以被其他线程重新设置。用volatile关键字来声明变量,就可以告诉编译器在编译的时候,不需要通过预测变量值来优化这部分的代码。
无法访问的Java线程死锁有时候虽然获取对象锁没有问题,线程依然有可能进入阻塞状态。在 Java 编程中IO就是这类问题最好的例子。当线程因为对象内的IO调用而阻塞时,此对象应当仍能被其他线程访问。该对象通常有责任取消这个阻塞的IO操作。造成阻塞调用的线程常常会令同步任务失败。如果该对象的其他方法也是同步的,当线程被阻塞时,此对象也就相当于被冷冻住了。
其他的线程由于不能获得对象的Java线程死锁,就不能给此对象发消息(例如,取消 IO 操作)。必须确保不在同步代码中包含那些阻塞调用,或确认在一个用同步阻塞代码的对象中存在非同步方法。尽管这种方法需要花费一些注意力来保证结果代码安全运行,但它允许在拥有对象的线程发生阻塞后,该对象仍能够响应其他线程。
=======================================================================
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性的访问权。当线程访问对象时,线程会给对象加锁,而这个锁导致其它也想访问同一对象的线程被阻塞,直至第一个线程释放它加在对象上的锁。
由于这个原因,在使用“synchronized”关键词时,很容易出现两个线程互相等待对方做出某个动作的情形。代码一是一个导致死锁的简单例子。
//代码一
- class Deadlocker {
- int field_1;
- private Object lock_1 = new int[];
- int field_2;
- private Object lock_2 = new int[];
- public void method1(int value) {
- “synchronized” (lock_1) {
- “synchronized” (lock_2) {
- field_1 = ; field_2 = ;
- }
- }
- }
- public void method2(int value) {
- “synchronized” (lock_2) {
- “synchronized” (lock_1) {
- field_1 = ; field_2 = ;
- }
- }
- }
- }
- class Deadlocker {
- int field_1;
- private Object lock_1 = new int[1];
- int field_2;
- private Object lock_2 = new int[1];
- public void method1(int value) {
- “synchronized” (lock_1) {
- “synchronized” (lock_2) {
- field_1 = 0; field_2 = 0;
- }
- }
- }
- public void method2(int value) {
- “synchronized” (lock_2) {
- “synchronized” (lock_1) {
- field_1 = 0; field_2 = 0;
- }
- }
- }
- }
class Deadlocker {
int field_1;
private Object lock_1 = new int[1];
int field_2;
private Object lock_2 = new int[1]; public void method1(int value) {
“synchronized” (lock_1) {
“synchronized” (lock_2) {
field_1 = 0; field_2 = 0;
}
}
} public void method2(int value) {
“synchronized” (lock_2) {
“synchronized” (lock_1) {
field_1 = 0; field_2 = 0;
}
}
}
}
参考代码一,考虑下面的过程:
◆ 一个线程(ThreadA)调用method1()。
◆ ThreadA在lock_1上同步,但允许被抢先执行。
◆ 另一个线程(ThreadB)开始执行。
◆ ThreadB调用method2()。
◆ ThreadB获得lock_2,继续执行,企图获得lock_1。但ThreadB不能获得lock_1,因为ThreadA占有lock_1。
◆ 现在,ThreadB阻塞,因为它在等待ThreadA释放lock_1。
◆ 现在轮到ThreadA继续执行。ThreadA试图获得lock_2,但不能成功,因为lock_2已经被ThreadB占有了。
◆ ThreadA和ThreadB都被阻塞,程序死锁。
当然,大多数的死锁不会这么显而易见,需要仔细分析代码才能看出,对于规模较大的多线程程序来说尤其如此。好的线程分析工具,例如JProbe Threadalyzer能够分析死锁并指出产生问题的代码位置。
隐性死锁 隐性死锁由于不规范的编程方式引起,但不一定每次测试运行时都会出现程序死锁的情形。由于这个原因,一些隐性死锁可能要到应用正式发布之后才会被发现,因此它的危害性比普通死锁更大。下面介绍两种导致隐性死锁的情况:加锁次序和占有并等待。
加锁次序
当多个并发的线程分别试图同时占有两个锁时,会出现加锁次序冲突的情形。如果一个线程占有了另一个线程必需的锁,就有可能出现死锁。考虑下面的情形,ThreadA和ThreadB两个线程分别需要同时拥有lock_1、lock_2两个锁,加锁过程可能如下:
◆ ThreadA获得lock_1;
◆ ThreadA被抢占,VM调度程序转到ThreadB;
◆ ThreadB获得lock_2;
◆ ThreadB被抢占,VM调度程序转到ThreadA;
◆ ThreadA试图获得lock_2,但lock_2被ThreadB占有,所以ThreadA阻塞;
◆ 调度程序转到ThreadB;
◆ ThreadB试图获得lock_1,但lock_1被ThreadA占有,所以ThreadB阻塞;
◆ ThreadA和ThreadB死锁。
必须指出的是,在代码丝毫不做变动的情况下,有些时候上述死锁过程不会出现,VM调度程序可能让其中一个线程同时获得lock_1和lock_2两个锁,即线程获取两个锁的过程没有被中断。在这种情形下,常规的死锁检测很难确定错误所在。
占有并等待
如果一个线程获得了一个锁之后还要等待来自另一个线程的通知,可能出现另一种隐性死锁,考虑代码二。
//代码二
- public class queue {
- static java.lang.Object queueLock_;
- Producer producer_;
- Consumer consumer_;
- public class Producer {
- void produce() {
- while (!done) {
- “synchronized” (queueLock_) {
- produceItemAndAddItToQueue();
- “synchronized” (consumer_) {
- consumer_.notify();
- }
- }
- }
- }
- public class Consumer {
- consume() {
- while (!done) {
- “synchronized” (queueLock_) {
- “synchronized” (consumer_) {
- consumer_.wait();
- }
- removeItemFromQueueAndProcessIt();
- }
- }
- }
- }
- }
- }
- public class queue {
- static java.lang.Object queueLock_;
- Producer producer_;
- Consumer consumer_;
- public class Producer {
- void produce() {
- while (!done) {
- “synchronized” (queueLock_) {
- produceItemAndAddItToQueue();
- “synchronized” (consumer_) {
- consumer_.notify();
- }
- }
- }
- }
- public class Consumer {
- consume() {
- while (!done) {
- “synchronized” (queueLock_) {
- “synchronized” (consumer_) {
- consumer_.wait();
- }
- removeItemFromQueueAndProcessIt();
- }
- }
- }
- }
- }
- }
public class queue {
static java.lang.Object queueLock_;
Producer producer_;
Consumer consumer_; public class Producer {
void produce() {
while (!done) {
“synchronized” (queueLock_) {
produceItemAndAddItToQueue();
“synchronized” (consumer_) {
consumer_.notify();
}
}
}
} public class Consumer {
consume() {
while (!done) {
“synchronized” (queueLock_) {
“synchronized” (consumer_) {
consumer_.wait();
}
removeItemFromQueueAndProcessIt();
}
}
}
}
}
}
在代码二中,Producer向队列加入一项新的内容后通知Consumer,以便它处理新的内容。问题在于,Consumer可能保持加在队列上的锁,阻止Producer访问队列,甚至在Consumer等待Producer的通知时也会继续保持锁。这样,由于Producer不能向队列添加新的内容,而Consumer却在等待Producer加入新内容的通知,结果就导致了死锁。
在等待时占有的锁是一种隐性的死锁,这是因为事情可能按照比较理想的情况发展—Producer线程不需要被Consumer占据的锁。尽管如此,除非有绝对可靠的理由肯定Producer线程永远不需要该锁,否则这种编程方式仍是不安全的。有时“占有并等待”还可能引发一连串的线程等待,例如,线程A占有线程B需要的锁并等待,而线程B又占有线程C需要的锁并等待等。
要改正代码二的错误,只需修改Consumer类,把wait()移出“synchronized”()即可。
因此避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C。 此外,Thread类的suspend()方法也很容易导致死锁,因此这个方法已经被废弃了.
Java死锁及解决的更多相关文章
- java 死锁及解决
Java线程死锁如何避免这一悲剧 Java线程死锁需要如何解决,这个问题一直在我们不断的使用中需要只有不断的关键.不幸的是,使用上锁会带来其他问题.让我们来看一些常见问题以及相应的解决方法: Jav ...
- Java程序中解决数据库超时与死锁
Java程序中解决数据库超时与死锁 2011-06-07 11:09 佚名 帮考网 字号:T | T Java程序中解决数据库超时与死锁,每个使用关系型数据库的程序都可能遇到数据死锁或不可用的情况 ...
- Java死锁排查和Java CPU 100% 排查的步骤整理
================================================= 人工智能教程.零基础!通俗易懂!风趣幽默!大家可以看看是否对自己有帮助! 点击查看高清无码教程 == ...
- 一文学会Java死锁和CPU 100% 问题的排查技巧
做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开 工欲善其事,必先利其器 00 本文简介 作为一名搞技术的程序猿或者是攻城狮,想必你应该是对下面这两个问题有所了解,说不定你在 ...
- 【性能优化】一文学会Java死锁和CPU100%问题的排查技巧
原文链接: 00 本文简介 作为一名搞技术的程序猿或者是攻城狮,想必你应该是对下面这两个问题有所了解,说不定你在实际的工作或者面试就有遇到过: 第一个问题:Java死锁如何排查和解决? 第二个问题:服 ...
- 实例详解 Java 死锁与破解死锁
锁和被保护资源之间的关系 我们把一段需要互斥执行的代码称为临界区.线程在进入临界区之前,首先尝试加锁 lock(),如果成功,则进入临界区,此时我们称这个线程持有锁:否则呢就等待,直到持有锁的线程解锁 ...
- java中文乱码解决之道(九)-----总结
乱码,我们前台展示的杀手,可能有些朋友和我的经历一样:遇到乱码先按照自己的经验来解决,如果没有解决就google,运气好一搜就可以解决,运气不好可能够你折腾一番了.LZ之所以写这个系列博客就是因为遇到 ...
- java中文乱码解决之道(二)-----字符编码详解:基础知识 + ASCII + GB**
在上篇博文(java中文乱码解决之道(一)-----认识字符集)中,LZ简单介绍了主流的字符编码,对各种编码都是点到为止,以下LZ将详细阐述字符集.字符编码等基础知识和ASCII.GB的详情. 一.基 ...
- java中文乱码解决之道(七)-----JSP页面编码过程
我们知道JSP页面是需要转换为servlet的,在转换过程中肯定是要进行编码的.在JSP转换为servlet过程中下面一段代码起到至关重要的作用. <%@ page language=" ...
随机推荐
- leetcode 【 Copy List with Random Pointer 】 python 实现
题目: A linked list is given such that each node contains an additional random pointer which could poi ...
- Python-S9-Day127-Scrapy爬虫框架2
01 今日内容概要 02 内容回顾:爬虫 03 内容回顾:并发和网络 04 Scrapy框架:起始请求定制 05 Scrapy框架:深度和优先级 06 Scrapy框架:内置代理 07 Scrapy框 ...
- git+jenkins持续集成三-定时构建语法
构建位置:选择或创建工程_设置_构建触发器 1. 定时构建语法:* * * * * (五颗星,多个时间点,中间用逗号隔开)第一个*表示分钟,取值0~59第二个*表示小时,取值0~23第三个*表示一个月 ...
- ACM-ICPC 2018 焦作赛区网络预赛
这场打得还是比较爽的,但是队友差一点就再过一题,还是难受啊. 每天都有新的难过 A. Magic Mirror Jessie has a magic mirror. Every morning she ...
- 关于JavaWeb开发的一些感悟
从事JavaWeb的开发已经三年了,从最开始的啥都不会,到慢慢的能够独立做项目,从一开始的一片茫然,到现在的心中有数.对于技术.业务也有了自己的看法. JavaWeb开发所涉及到的知识点非常多,涉及到 ...
- linux系统——etc下的profile文件
/etc/profile文件 /etc/profile是全局的,适用于所有的shell.在刚登录Linux时,首先启动 /etc/profile 文件. profile文件会告诉shell使用什么语言 ...
- 逆序对(inversion)
逆序对(inversion) 题目描述 对于序列AA,它的逆序对数定义为满足i<ji<j,且Ai>AjAi>Aj 的数对i,ji,j的个数. 现给你11到nn的一个排列,并按照 ...
- openssl Rsa 分段加密解密
密钥长度 1024 openssl genrsa -out rsa_private_key.pem openssl rsa -in rsa_private_key.pem -pubout -out r ...
- JavaScript (JS) 函数补充 (含arguments、eval()、四种调用模式)
1. 程序异常 ① try-catch语法 测试异常 try-catch语法代码如下: try { 异常代码; try中可以承重异常代码, console.log(“try”) 出现异 ...
- 【CF1016C】Vasya And The Mushrooms(模拟)
题意:给定一个2*n的矩阵,每一个点有一个权值,从左上角出发,时间t=0开始,连续的走,将矩阵走完, 每走一步,t++,并且得到t*当前格子的权值的值,求最大的权值和 n<=3e5,1<= ...