等待时持有的锁是隐式死锁. 这是因为事情可能会在理想的情况下发展—生产者线程不需要消费者持有的锁. 但是,除非有绝对可靠的理由确保Producer线程永远不需要锁,否则此编程方法仍然不安全. 有时“拥有并等待”可能导致一系列线程等待. 例如,线程A持有线程B所需的锁并等待,而线程B持有线程C所需的锁并等待.
要纠正代码2中的错误,只需修改Consumer类,然后将wait()从“ synchronized”()中移出.
因此,避免死锁的一般经验法则是: 当多个线程必须访问共享资源A,B,C时,请确保每个线程以相同的顺序访问它们,例如首先访问A,访问B和B时. C.
此外,Thread类的suspend()方法也很容易引起死锁,因此该方法已被放弃.
数据竞争
数据竞争是由访问共享资源(例如变量)时缺少或不适当使用同步机制引起的. 如果某个线程在某个时间不能访问该变量,则会发生数据竞争. 此时,赢得比赛的线程将获得访问权限,但会导致不可预测的结果.
由于线程的运行可以随时中断(也就是说,运行机会被其他线程抢占了),因此不能假定首先开始运行的线程始终在访问该线程之前访问共享数据稍后开始. 另外,在不同的虚拟机上,线程调度方法也可能不同,从而使数据竞争问题更加复杂.
有时候,数据竞争不会影响程序的最终运行结果,但有时可能会导致不可预测的结果.
有利的数据竞争
并非所有数据竞争都是错误的. 考虑代码三的示例. 假设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(500);
}
catch (Exception e) {
System.err.println(“Exception:” + e);
}
}
}
}
}
尽管存在竞争,但根据Java VM规范,原则上是布尔数据的读取和写入,也就是说,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尝试同时通过不同的银行柜员机将钱存入同一帐户,会发生什么情况?让我们假设该帐户的初始余额为100元,并查看该程序的可能执行情况.
B存款25元,她的ATM机开始执行存款(). 首先获得当前余额100,然后将此余额保存在本地临时变量中,然后将25添加到临时变量中,并且临时变量的值变为125. 现在,在调用setBalance()之前,线程调度程序将中断线程.
押金50元. 当B的线程仍处于暂停状态时,A开始执行deposit(): getBalance()返回100(因为B的线程此时尚未写入修改后的余额),A的线程在现有余额中加50以得到150 ,然后将值150保存到一个临时变量中. 然后,在调用setBalance()之前,A的线程也会中断.
现在,B的线程然后运行并将存储在临时变量中的值(125)写入天平. 出纳员告诉B交易已经完成,账户余额为125元. 接下来,A的线程继续运行,将临时变量(150)的值写入余额,出纳员告诉A交易已完成,帐户余额为150元.
最终结果是什么? B的存款消失了,就像B从未存过钱一样.
也许有人认为您可以将getBalance()和setBalance()更改为同步方法以保护Account.balance_并解决数据竞争问题. 实际上,此方法无效. “ synchronized”关键字可以确保只有一个线程同时执行getBalance()或setBalance()方法,但这不能防止另一个线程在一个线程的操作期间修改帐户余额.
要正确使用“ synchronized”关键字,您必须意识到整个事务处理过程都将受到保护,以免受到另一个线程的干扰,而不仅仅是保护数据访问的特定步骤.
因此,此示例的关键是在一个线程获得当前余额之后,有必要确保其他线程无法修改余额,直到完成第一个线程的余额处理为止. 正确的修改方法是将deposit()和提现()更改为同步方法.
死锁,隐式死锁和数据争用是Java多线程编程中最常见的错误. 要编写健壮的多线程代码,正确理解和使用“ synchronized”关键字很重要. 此外,良好的线程分析工具(例如JProbe Threadalyzer)可以大大简化错误检测. 分析工具对于分析每次执行时不一定发生的错误特别有用.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-227888-2.html
就你这智商也学人借打假之名来坑人