知识大全 多线程Java程序中常见错误的巧处理

Posted 余额

篇首语:捐躯赴国难,视死忽如归。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 多线程Java程序中常见错误的巧处理相关的知识,希望对你有一定的参考价值。

多线程Java程序中常见错误的巧处理  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

 在几乎所有编程语言中 由于多线程引发的错误都有着难以再现的特点 程序的死锁或其它多线程错误可能只在某些特殊的情形下才出现 或在不同的VM上运行同一个程序时错误表现不同 因此 在编写多线程程序时 事先认识和防范可能出现的错误特别重要

  无论是客户端还是服务器端多线程Java程序 最常见的多线程问题包括死锁 隐性死锁和数据竞争

  一 死锁

  死锁是这样一种情形 多个线程同时被阻塞 它们中的一个或者全部都在等待某个资源被释放 由于线程被无限期地阻塞 因此程序不可能正常终止

  导致死锁的根源在于不适当地运用 synchronized 关键词来管理线程对特定对象的访问 synchronized 关键词的作用是 确保在某个时刻只有一个线程被允许执行特定的代码块 因此 被允许执行的线程首先必须拥有对变量或对象的排他性的访问权 当线程访问对象时 线程会给对象加锁 而这个锁导致其它也想访问同一对象的线程被阻塞 直至第一个线程释放它加在对象上的锁

  由于这个原因 在使用 synchronized 关键词时 很容易出现两个线程互相等待对方做出某个动作的情形 代码一是一个导致死锁的简单例子

//代码一class Deadlocker int field_ ;private Object lock_ = new int[ ];int field_ ;private Object lock_ = new int[ ];

public void method (int value) synchronized (lock_ ) synchronized (lock_ ) field_ = ; field_ = ;

public void method (int value) synchronized (lock_ ) synchronized (lock_ ) field_ = ; field_ = ;

  参考代码一 考虑下面的过程

  ◆ 一个线程(ThreadA)调用method ()

  ◆ ThreadA在lock_ 上同步 但允许被抢先执行

  ◆ 另一个线程(ThreadB)开始执行

  ◆ ThreadB调用method ()

  ◆ ThreadB获得lock_ 继续执行 企图获得lock_ 但ThreadB不能获得lock_ 因为ThreadA占有lock_

  ◆ 现在 ThreadB阻塞 因为它在等待ThreadA释放lock_

  ◆ 现在轮到ThreadA继续执行 ThreadA试图获得lock_ 但不能成功 因为lock_ 已经被ThreadB占有了

  ◆ ThreadA和ThreadB都被阻塞 程序死锁

  当然 大多数的死锁不会这么显而易见 需要仔细分析代码才能看出 对于规模较大的多线程程序来说尤其如此 好的线程分析工具 例如JProbe Threadalyzer能够分析死锁并指出产生问题的代码位置

  二 隐性死锁

  隐性死锁由于不规范的编程方式引起 但不一定每次测试运行时都会出现程序死锁的情形 由于这个原因 一些隐性死锁可能要到应用正式发布之后才会被发现 因此它的危害性比普通死锁更大 下面介绍两种导致隐性死锁的情况 加锁次序和占有并等待

   加锁次序

  当多个并发的线程分别试图同时占有两个锁时 会出现加锁次序冲突的情形 如果一个线程占有了另一个线程必需的锁 就有可能出现死锁 考虑下面的情形 ThreadA和ThreadB两个线程分别需要同时拥有lock_ lock_ 两个锁 加锁过程可能如下

  ◆ ThreadA获得lock_

  ◆ ThreadA被抢占 VM调度程序转到ThreadB

  ◆ ThreadB获得lock_

  ◆ ThreadB被抢占 VM调度程序转到ThreadA

  ◆ ThreadA试图获得lock_ 但lock_ 被ThreadB占有 所以ThreadA阻塞

  ◆ 调度程序转到ThreadB

  ◆ ThreadB试图获得lock_ 但lock_ 被ThreadA占有 所以ThreadB阻塞

  ◆ ThreadA和ThreadB死锁

  必须指出的是 在代码丝毫不做变动的情况下 有些时候上述死锁过程不会出现 VM调度程序可能让其中一个线程同时获得lock_ 和lock_ 两个锁 即线程获取两个锁的过程没有被中断 在这种情形下 常规的死锁检测很难确定错误所在

   占有并等待

  如果一个线程获得了一个锁之后还要等待来自另一个线程的通知 可能出现另一种隐性死锁 考虑代码二

//代码二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 ()即可

  三 数据竞争

  数据竞争是由于访问共享资源(例如变量)时缺乏或不适当地运用同步机制引起 如果没有正确地限定某一时刻某一个线程可以访问变量 就会出现数据竞争 此时赢得竞争的线程获得访问许可 但会导致不可预知的结果

  由于线程的运行可以在任何时候被中断(即运行机会被其它线程抢占) 所以不能假定先开始运行的线程总是比后开始运行的线程先访问到两者共享的数据 另外 在不同的VM上 线程的调度方式也可能不同 从而使数据竞争问题更加复杂

  有时 数据竞争不会影响程序的最终运行结果 但在另一些时候 有可能导致不可预料的结果

   良性数据竞争

  并非所有的数据竞争都是错误 考虑代码三的例子 假设getHouse()向所有的线程返回同一House 可以看出 这里会出现竞争 BrickLayer从House foundationReady_读取 而FoundationPourer写入到House foundationReady_

//代码三public class House public volatile boolean foundationReady_ = false;

public class FoundationPourer extends Thread public void run() House a = getHouse();a foundationReady_ = true;

public class BrickLayer extends Thread public void run() House a = getHouse();while (!a foundationReady_) try Thread sleep( );catch (Exception e) System err println( Exception: + e);

  尽管存在竞争 但根据Java VM规范 Boolean数据的读取和写入都是原则性的 也就是说 VM不能中断线程的读取或写入操作 一旦数据改动成功 不存在将它改回原来数据的必要(不需要 回退 ) 所以代码三的数据竞争是良性竞争 代码是安全的

   恶性数据竞争

  首先看一下代码四的例子

//代码四public class Account private int balance_; // 账户余额public int getBalance(void) return balance_;public void setBalance(int setting) balance_ = setting;

public class CustomerInfo private int numAccounts_;private Account[] accounts_;public void withdraw(int accountNumber int amount) int temp = accounts_[accountNumber] getBalance();temp = temp amount;accounts_[accountNumber] setBalance(temp);public void deposit(int accountNumber int amount) int temp = accounts_[accountNumber] getBalance();temp = temp + amount;accounts_[accountNumber] setBalance(temp);

  如果丈夫A和妻子B试图通过不同的银行柜员机同时向同一账户存钱 会发生什么事情?让我们假设账户的初始余额是 元 看看程序的一种可能的执行经过

  B存钱 元 她的柜员机开始执行deposit() 首先取得当前余额 把这个余额保存在本地的临时变量 然后把临时变量加 临时变量的值变成 现在 在调用setBalance()之前 线程调度器中断了该线程

  A存入 元 当B的线程仍处于挂起状态时 A这面开始执行deposit() getBalance()返回 (因为这时B的线程尚未把修改后的余额写入) A的线程在现有余额的基础上加 得到 并把 这个值保存到临时变量 接着 A的线程在调用setBalance()之前 也被中断执行

  现在 B的线程接着运行 把保存在临时变量中的值( )写入到余额 柜员机告诉B说交易完成 账户余额是 元 接下来 A的线程继续运行 把临时变量的值( )写入到余额 柜员机告诉A说交易完成 账户余额是 元

  最后得到的结果是什么?B的存款消失不见 就像B根本没有存过钱一样

  也许有人会认为 可以把getBalance()和setBalance()改成同步方法保护Account balance_ 解决数据竞争问题 其实这种办法是行不通的 synchronized 关键词可以确保同一时刻只有一个线程执行getBalance()或setBalance()方法 但这不能在一个线程操作期间阻止另一个线程修改账户余额

要正确运用 synchronized 关键词 就必须认识到这里要保护的是整个交易过程不被另一个线程干扰 而不仅仅是对数据访问的某一个步骤进行保护

  所以 本例的关键是当一个线程获得当前余额之后 要保证其它的线程不能修改余额 直到第一个线程的余额处理工作全部完成 正确的修改方法是把deposit()和withdraw()改成同步方法

cha138/Article/program/Java/gj/201311/27782

相关参考

知识大全 java多线程中的异常处理

  在java多线程程序中所有线程都不允许抛出未捕获的checkedexception也就是说各个线程需要自己把自己的checkedexception处理掉这一点是通过javalangRunnable

知识大全 Java程序中的多线程

Java程序中的多线程  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  一为什么会排队等待?  下

知识大全 Java 程序中的多线程(四)

Java程序中的多线程(四)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  作者NeelVKum

知识大全 Java 程序中的多线程(二)

Java程序中的多线程(二)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  作者NeelVKum

知识大全 Java 程序中的多线程(一)

Java程序中的多线程(一)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  作者NeelVKum

知识大全 多线程在JAVA ME应用程序中的使用

多线程在JAVAME应用程序中的使用  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  多线程技术是

知识大全 Java多线程程序如何掌握基本语法

Java多线程程序如何掌握基本语法  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Java多线程

知识大全 Java语言深入 多线程程序模型研究

Java语言深入多线程程序模型研究  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  多线程是较复杂

知识大全 构建多线程Java应用程序

构建多线程Java应用程序  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  大多数服务端应用程序都

知识大全 Java多线程进程应对同一程序运行资源

Java多线程进程应对同一程序运行资源  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Java多