重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
创新互联建站主要从事做网站、网站设计、网页设计、企业做网站、公司建网站等业务。立足成都服务上栗,十余年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:18982081108
导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性的访问权。当线程访问对象时,线程会给对象加锁,而这个锁导致其它也想访问同一对象的线程被阻塞,直至第一个线程释放它加在对象上的锁。
由于这个原因,在使用“synchronized”关键词时,很容易出现两个线程互相等待对方做出某个动作的情形。代码一是一个导致死锁的简单例子。
//代码一
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();
}
}
}
}
}
}
在代码二中,Producer向队列加入一项新的内容后通知Consumer,以便它处理新的内容。问题在于,Consumer可能保持加在队列上的锁,阻止Producer访问队列,甚至在Consumer等待Producer的通知时也会继续保持锁。这样,由于Producer不能向队列添加新的内容,而Consumer却在等待Producer加入新内容的通知,结果就导致了死锁。
在等待时占有的锁是一种隐性的死锁,这是因为事情可能按照比较理想的情况发展—Producer线程不需要被Consumer占据的锁。尽管如此,除非有绝对可靠的理由肯定Producer线程永远不需要该锁,否则这种编程方式仍是不安全的。有时“占有并等待”还可能引发一连串的线程等待,例如,线程A占有线程B需要的锁并等待,而线程B又占有线程C需要的锁并等待等。
要改正代码二的错误,只需修改Consumer类,把wait()移出“synchronized”()即可。
每个使用关系型数据库的程序都可能遇到数据死锁或不可用的情况,而这些情况需要在代码中编程来解决;本文主要介绍与数据库事务死锁等情况相关的重试逻辑概念,此外,还会探讨如何避免死锁等问题,文章以DB2(版本9)与为例进行讲解。
什么是数据库锁定与死锁锁定(Locking)发生在当一个事务获得对某一资源的“锁”时,这时,其他的事务就不能更改这个资源了,这种机制的存在是为了保证数据一致性;在设计与数据库交互的程序时,必须处理锁与资源不可用的情况。
锁定是个比较复杂的概念,仔细说起来可能又需要一大篇,所以在本文中,只把锁定看作是一个临时事件,这意味着如果一个资源被锁定,它总会在以后某个时间被释放。
而死锁发生在当多个进程访问同一数据库时,其中每个进程拥有的锁都是其他进程所需的,由此造成每个进程都无法继续下去。
如何避免锁我们可利用事务型数据库中的隔离级别机制来避免锁的创建,正确地使用隔离级别可使程序处理更多的并发事件(如允许多个用户访问数据),还能预防像丢失修改(LostUpdate)、读“脏”数据(DirtyRead)、不可重复读(NonrepeatableRead)及“虚”(Phantom)等问题。
隔离级别问题现象丢失修改读“脏”数据不可重复读“虚”可重复读取NoNoNoNo读取稳定性NoNoNoYes光标稳定性NoNoYesYes未提交的读NoYesYesYes表1:DB2的隔离级别与其对应的问题现象在只读模式中,就可以防止锁定发生,而不用那些未提交只读隔离级别的含糊语句。
浙江电脑培训发现一条SQL语句当使用了下列命令之一时,就应该考虑只读模式了
在基于Java Swing进行图形界面开发的时候 经常遇到的就是Swing多线程问题 我们可以想想一下 如果需要在一个图形界面上显示很多数据 这些数据是经过长时间 复杂的查询和运算得到的 如果在图形界面的同一个线程中进行查询和运算工作则会导致一段时间界面处于死机状态 这会给用户带来不良的互动感受 为了解决这个问题 一般会单独启动一个线程进行运算和查询工作 并随时更新图形界面 这时候 另一个问题就出现了 可能不仅没有解决原来偶尔死机问题 还可能导致程序彻底死掉 幸运的是在JDK中暗藏了一个中断程序的快捷键 就是CTRL+BREAK 这个快捷键Sun并没有在文档中公布 如果在命令行模式下启动Java程序 然后按CTRL+BREAK键 会得到堆栈的跟踪信息 从这些跟踪信息中就可以知道具体引发死机的位置了
当一个程序产生死锁的时候 你一定会希望尽快找到原因并且解决它 这时候 你一般的精力会用在查找引发死锁的位置 另一半的精力会用于对堆栈进行跟踪一确定引发死锁的原因 但是在Java Swing程序中 你的所有努力可能都是没有价值的 这是因为Java对Swing的多线程编程有一个特殊要求 就是在Swing里 只能在与Swing相同的线程里对GUI元件进行修改
也就是说 如果你要执行类似于jLabel setText( blabla )代码 必须在Swing线程中 而不允许在其他线程当中 如果必须在其他线程中修改元件 可以使用类似一下方式解决
SwingUtilities invokeLater(new Runnable() {
public void run() {
jLabel setText( blabla );
}
}
invokeLater方法虽然表面有时间延迟执行含义 但是实际上几乎没有任何影响 可能在几毫秒之内就会被执行 另外还有一个invokeAndWait方法 除非特殊需要 否则几乎是不用的
在不使用invokeLater的情况下 导致刷新问题是可以理解的 但是导致死锁就优点令人匪夷所思了 幸运的是 不是任何时候都需要调用改方法 这是因为大多数情况下 我们都是在与Swing同一个线程里进行界面更新 例如监听按钮点击事件的ActionListener actionPerformed方法就是运行在与Swing相同的线程中的 但是如果在回调类中引用了另一个类 并且是不属于AWT/Swing的 那么结果就很难确定了 所以说使用invokeLater应该是最安全的
需要注意的是 在invokeLater做的任何事情 都会导致Swing线程窗口绘制工作暂停下来 等候invokeLater工作结束 所以不要在invokeLater进行耗时操作 尽量只执行那些界面绘制相关的工作 可以通过代码重构 将那些与界面更新相关的代码集中起来统一处理
lishixinzhi/Article/program/Java/gj/201311/27498