【条件竞争
在多线程的开发中,两个及其以上的线程需要共享统一数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用一个修改该对象状态的方法,根据线程访问数据的顺序,可能会出现错误的数据结果,这种现象成为条件竞争。因为修改对象状态的方法并不是一个原子操作,通常步骤是:
1. 读取当前状态值到线程工作内存。
2. 在线程工作内存中修改状态值。
3. 将修改后的状态值重新写入主内存。
而问题往往就是有多个线程同时在执行步骤2。
【 有两种机制代码受并发访问的干扰
- synchronized关键字。
- Reentrantlock类。
【synchronized关键字
java中每个对象都有一个内部锁。如果一个方法是用synchronized关键字修声明的,那么对象的锁将保护整个方法,也就是说要调用该方法,线程必须获得对象内部锁。内部锁有如下的特点:
- 不能中断正在试图获得锁的线程。
- 试图获得锁不能设置超时时间。
- 只有一个条件:要么获得,要么等待,没有粒度更细的控制。
【多个线程修改同一个对象造成数据失误
public class Sync {
private int value;
void add(){
this.value ++;
}
int getValue(){
return this.value;
}
public static void main(String[] args) {
for (int i = 0; i <30 ; i++) {
Sync s = new Sync();
List<CompletableFuture> cfs = new ArrayList<>();
cfs.add(CompletableFuture.runAsync(() -> s.add()));
cfs.add(CompletableFuture.runAsync(() -> s.add()));
cfs.add(CompletableFuture.runAsync(() -> s.add()));
//等待子线程执行完毕
CompletableFuture.allOf(cfs.toArray(new CompletableFuture[cfs.size()])).join();
System.out.println(s.getValue());
}
}
}
现在使用synchronized关键字修饰add()看看:数据正常。
【注意
如果我们在同步的时候需要判断,切记将条件判断放在同步代码块之中,如果在外部判断,很有可能出现:
1. 第一个线程通过条件判断
2. 第二个线程获得cpu,并修改了共享对象
3. 第一个线程再次获得cpu此时已经不满足条件,但是代码在向下执行,于是出现异常的情况。
例如转账的例子:
public class Account {
//账户金额
private int amount;
public Account(int amount){
this.amount = amount;
}
//转账
public synchronized void trans(Account to,int value){
//条件判断处于同步代码中!!!!!!
if(amount - value < 0){
throw new RuntimeException("钱不够了!");
}
this.amount -= value;
to.addAmount(value);
System.out.println("转账成功");
}
public void addAmount(int amount){
this.amount += amount;
}
public int getAmount(){
return this.amount;
}
public static void main(String[] args) {
Account from = new Account(100);
Account to1 = new Account(0);
Account to2 = new Account(0);
List<CompletableFuture> cfs = new ArrayList<>();
cfs.add(CompletableFuture.runAsync(() -> from.trans(to1,100)));
cfs.add(CompletableFuture.runAsync(() -> from.trans(to2,100)));
CompletableFuture.allOf(cfs.toArray(new CompletableFuture[cfs.size()])).join();
System.out.println(from.getAmount());
System.out.println(to1.getAmount());
System.out.println(to2.getAmount());
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。