在一个大型项目中通常会有着上千上万个线程,线程与线程之间发生冲突是怎么解决的,比如你的两个账户同时向同一家银行发送申请一个存钱一个取钱,那钱的变化要怎么处理。这就是线程安全问题。

下面我们可以模拟一个银行存钱和取钱的场景:

public class Bank {
    public double money;
    public Bank(double money){
        this.money=money;
    }
    public void getMoney(double catchNum){
        money-=catchNum;
    }
    public void saveMoney(double saveNum){
        money+=saveNum;
    }
}

创建一个bank类,添加money成员和存钱取钱的方法。

接着创建一个线程类的子类,向其中添加对象和编写线程的内容。

public class UserThread extends Thread{
    public Bank bank;
    public boolean isCatchMoney;
    public double money;
    public UserThread(Bank bank,boolean b,double money){
        this.bank=bank;
        this.isCatchMoney=b;
        this.money=money;
    }
    @Override
    public void run() {     
            System.out.println("用户拥有:" + bank.money);
            if (isCatchMoney) {
                bank.getMoney(money);
            } else {
                bank.saveMoney(money);
            }
            System.out.println("用户剩余:" + bank.money);
    }
}

这里的线程可以分为多个步骤,在我们创建多个线程并同时经行调用时,可能就会出现操作权的转移,一个线程运行一半后转移到另一个线程继续执行。我们用main函数调用上面的线程:

  public static void main(String[] args) {
        Bank bank=new Bank(100);
        UserThread u1=new UserThread(bank,true,30);
        UserThread u2=new UserThread(bank,false,40);
        u1.start();
        u2.start();
    }

结果如下:

按理来说我们执行完成u1时,在执行u2时会显示用户拥有70,这里是执行权发生了变化,u1执行第一个输出后执行u2的输出,在执行u1的判断和u2的判断,最后输出。执行权的变化是因为线程中的语句不是原子操作。

原子操作是一个不可以继续分割的操作,比如int i=1;不是原子操作的最典型的就是多个语句执行。所以我们要用一把锁把这些语句给锁着形成一种类似原子操作的结构,我们可以利用synchronized (对象名){},这里的对象要填一个统一的对象,这样才能保证按顺序执行被锁住的内容。

我们将线程改为如下方法:

public void run() {
        synchronized (bank){
            System.out.println("用户拥有:" + bank.money);
            if (isCatchMoney) {
                bank.getMoney(money);
            } else {
                bank.saveMoney(money);
            }
            System.out.println("用户剩余:" + bank.money);
        }
    }

这样的输出结果就是我们想要的了:

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐