1

【条件竞争

在多线程的开发中,两个及其以上的线程需要共享统一数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用一个修改该对象状态的方法,根据线程访问数据的顺序,可能会出现错误的数据结果,这种现象成为条件竞争。因为修改对象状态的方法并不是一个原子操作,通常步骤是:

1. 读取当前状态值到线程工作内存。
2. 在线程工作内存中修改状态值。
3. 将修改后的状态值重新写入主内存。

而问题往往就是有多个线程同时在执行步骤2。

【 有两种机制代码受并发访问的干扰

  1. synchronized关键字。
  2. Reentrantlock类。

【synchronized关键字

java中每个对象都有一个内部锁。如果一个方法是用synchronized关键字修声明的,那么对象的锁将保护整个方法,也就是说要调用该方法,线程必须获得对象内部锁。内部锁有如下的特点:

  1. 不能中断正在试图获得锁的线程。
  2. 试图获得锁不能设置超时时间。
  3. 只有一个条件:要么获得,要么等待,没有粒度更细的控制。

【多个线程修改同一个对象造成数据失误

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());
    }
}

极品公子
221 声望43 粉丝

山不向我走来,我便向山走去。