在编写多线程应用程序时,遇到的最常见的问题之一是竞争条件。
我对社区的问题是:
竞态条件是什么? 你如何发现它们? 你是如何处理的? 最后,你如何防止它们的发生?
在编写多线程应用程序时,遇到的最常见的问题之一是竞争条件。
我对社区的问题是:
竞态条件是什么? 你如何发现它们? 你是如何处理的? 最后,你如何防止它们的发生?
当前回答
为了更好地理解竞态条件,请尝试以下基本示例:
public class ThreadRaceCondition {
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Account myAccount = new Account(22222222);
// Expected deposit: 250
for (int i = 0; i < 50; i++) {
Transaction t = new Transaction(myAccount,
Transaction.TransactionType.DEPOSIT, 5.00);
t.start();
}
// Expected withdrawal: 50
for (int i = 0; i < 50; i++) {
Transaction t = new Transaction(myAccount,
Transaction.TransactionType.WITHDRAW, 1.00);
t.start();
}
// Temporary sleep to ensure all threads are completed. Don't use in
// realworld :-)
Thread.sleep(1000);
// Expected account balance is 200
System.out.println("Final Account Balance: "
+ myAccount.getAccountBalance());
}
}
class Transaction extends Thread {
public static enum TransactionType {
DEPOSIT(1), WITHDRAW(2);
private int value;
private TransactionType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
};
private TransactionType transactionType;
private Account account;
private double amount;
/*
* If transactionType == 1, deposit else if transactionType == 2 withdraw
*/
public Transaction(Account account, TransactionType transactionType,
double amount) {
this.transactionType = transactionType;
this.account = account;
this.amount = amount;
}
public void run() {
switch (this.transactionType) {
case DEPOSIT:
deposit();
printBalance();
break;
case WITHDRAW:
withdraw();
printBalance();
break;
default:
System.out.println("NOT A VALID TRANSACTION");
}
;
}
public void deposit() {
this.account.deposit(this.amount);
}
public void withdraw() {
this.account.withdraw(amount);
}
public void printBalance() {
System.out.println(Thread.currentThread().getName()
+ " : TransactionType: " + this.transactionType + ", Amount: "
+ this.amount);
System.out.println("Account Balance: "
+ this.account.getAccountBalance());
}
}
class Account {
private int accountNumber;
private double accountBalance;
public int getAccountNumber() {
return accountNumber;
}
public double getAccountBalance() {
return accountBalance;
}
public Account(int accountNumber) {
this.accountNumber = accountNumber;
}
// If this method is not synchronized, you will see race condition on
// Remove syncronized keyword to see race condition
public synchronized boolean deposit(double amount) {
if (amount < 0) {
return false;
} else {
accountBalance = accountBalance + amount;
return true;
}
}
// If this method is not synchronized, you will see race condition on
// Remove syncronized keyword to see race condition
public synchronized boolean withdraw(double amount) {
if (amount > accountBalance) {
return false;
} else {
accountBalance = accountBalance - amount;
return true;
}
}
}
其他回答
竞态条件是并发编程中的一种情况,其中两个并发线程或进程争夺资源,最终状态取决于谁先获得资源。
为了更好地理解竞态条件,请尝试以下基本示例:
public class ThreadRaceCondition {
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Account myAccount = new Account(22222222);
// Expected deposit: 250
for (int i = 0; i < 50; i++) {
Transaction t = new Transaction(myAccount,
Transaction.TransactionType.DEPOSIT, 5.00);
t.start();
}
// Expected withdrawal: 50
for (int i = 0; i < 50; i++) {
Transaction t = new Transaction(myAccount,
Transaction.TransactionType.WITHDRAW, 1.00);
t.start();
}
// Temporary sleep to ensure all threads are completed. Don't use in
// realworld :-)
Thread.sleep(1000);
// Expected account balance is 200
System.out.println("Final Account Balance: "
+ myAccount.getAccountBalance());
}
}
class Transaction extends Thread {
public static enum TransactionType {
DEPOSIT(1), WITHDRAW(2);
private int value;
private TransactionType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
};
private TransactionType transactionType;
private Account account;
private double amount;
/*
* If transactionType == 1, deposit else if transactionType == 2 withdraw
*/
public Transaction(Account account, TransactionType transactionType,
double amount) {
this.transactionType = transactionType;
this.account = account;
this.amount = amount;
}
public void run() {
switch (this.transactionType) {
case DEPOSIT:
deposit();
printBalance();
break;
case WITHDRAW:
withdraw();
printBalance();
break;
default:
System.out.println("NOT A VALID TRANSACTION");
}
;
}
public void deposit() {
this.account.deposit(this.amount);
}
public void withdraw() {
this.account.withdraw(amount);
}
public void printBalance() {
System.out.println(Thread.currentThread().getName()
+ " : TransactionType: " + this.transactionType + ", Amount: "
+ this.amount);
System.out.println("Account Balance: "
+ this.account.getAccountBalance());
}
}
class Account {
private int accountNumber;
private double accountBalance;
public int getAccountNumber() {
return accountNumber;
}
public double getAccountBalance() {
return accountBalance;
}
public Account(int accountNumber) {
this.accountNumber = accountNumber;
}
// If this method is not synchronized, you will see race condition on
// Remove syncronized keyword to see race condition
public synchronized boolean deposit(double amount) {
if (amount < 0) {
return false;
} else {
accountBalance = accountBalance + amount;
return true;
}
}
// If this method is not synchronized, you will see race condition on
// Remove syncronized keyword to see race condition
public synchronized boolean withdraw(double amount) {
if (amount > accountBalance) {
return false;
} else {
accountBalance = accountBalance - amount;
return true;
}
}
}
这个讨论中的许多答案解释了什么是竞态条件。我试图解释为什么这个术语在软件行业被称为竞争条件。
为什么叫竞态条件?
竞态条件不仅与软件有关,也与硬件有关。实际上,这个术语最初是由硬件行业创造的。
根据维基百科:
这个术语起源于两个信号相互竞争的想法 首先影响输出。 逻辑电路中的竞态:
软件行业没有对这个术语进行修改,这使得它有点难以理解。
你需要做一些替换来把它映射到软件世界:
"两个信号" ==> "两个线程"/"两个进程" "影响输出" ==> "影响一些共享状态"
因此,软件行业中的竞争条件是指“两个线程”/“两个进程”相互竞争以“影响某种共享状态”,而共享状态的最终结果将取决于一些微妙的时间差,而时间差可能是由某些特定的线程/进程启动顺序、线程/进程调度等引起的。
Race conditions occur in multi-threaded applications or multi-process systems. A race condition, at its most basic, is anything that makes the assumption that two things not in the same thread or process will happen in a particular order, without taking steps to ensure that they do. This happens commonly when two threads are passing messages by setting and checking member variables of a class both can access. There's almost always a race condition when one thread calls sleep to give another thread time to finish a task (unless that sleep is in a loop, with some checking mechanism).
防止竞争条件的工具依赖于语言和操作系统,但一些常见的工具是互斥锁、临界区和信号。互斥锁在你想确保你是唯一一个在做某事的时候很有用。当你想确保别人已经完成某件事时,信号是很好的。最小化共享资源还有助于防止意外行为
Detecting race conditions can be difficult, but there are a couple signs. Code which relies heavily on sleeps is prone to race conditions, so first check for calls to sleep in the affected code. Adding particularly long sleeps can also be used for debugging to try and force a particular order of events. This can be useful for reproducing the behavior, seeing if you can make it disappear by changing the timing of things, and for testing solutions put in place. The sleeps should be removed after debugging.
但是,如果某个问题只在某些机器上断断续续地发生,则是存在竞争条件的标志性标志。常见的错误是崩溃和死锁。使用日志记录,您应该能够找到受影响的区域并从那里返回。
当访问共享资源的多线程(或其他并行)代码可能以导致意外结果的方式访问共享资源时,就存在“竞争条件”。
举个例子:
for ( int i = 0; i < 10000000; i++ )
{
x = x + 1;
}
如果你有5个线程同时执行这段代码,x的值最终不会是50,000,000。事实上,它会随着每一次运行而变化。
这是因为,为了让每个线程增加x的值,它们必须做以下事情:(显然是简化的)
Retrieve the value of x Add 1 to this value Store this value to x
任何线程都可以在任何时间处于此进程的任何步骤,并且当涉及共享资源时,它们可以相互踩。在读取x和写回x之间的时间内,x的状态可以由另一个线程改变。
假设一个线程获取了x的值,但还没有存储它。另一个线程也可以检索相同的x值(因为还没有线程更改它),然后它们都将在x中存储相同的值(x+1) !
例子:
Thread 1: reads x, value is 7 Thread 1: add 1 to x, value is now 8 Thread 2: reads x, value is 7 Thread 1: stores 8 in x Thread 2: adds 1 to x, value is now 8 Thread 2: stores 8 in x
竞争条件可以通过在代码访问共享资源之前使用某种锁定机制来避免:
for ( int i = 0; i < 10000000; i++ )
{
//lock x
x = x + 1;
//unlock x
}
这里,答案每次都是50,000,000。
有关锁的更多信息,请搜索:互斥量,信号量,临界区,共享资源。