线程安全和解决的办法

一、线程安全

线程安全问题出现的原因:

1.存在多个线程同时运行

2.访问同一个共享资源

3.存在修改共享资源

案例: 银行取款

 线程安全和解决的办法_第1张图片

 直接上代码:模拟银行取款的线程安全问题

1.先创建一个账户类(Account)

public class Account {
    private String cardId;
    private double money;

    //小红和小明同时过来
    public void drawMoney(double money){
        //先判断谁来取钱
        String name = Thread.currentThread().getName();
        //判断余额是否充足
        if (this.money >= money){
            System.out.println(name + "来取钱,取钱成功!");
            this.money -= money;
            System.out.println(name + "来取钱后,账户余额" + this.money);
        }else{
            System.out.println(name + "来取钱,余额不足!");
        }
    }
    public String getCardId() {
        return cardId;
    }

    public double getMomey() {
        return money;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public void setMomey(double momey) {
        this.money = momey;
    }

    public Account(String cardId, double momey) {
        this.cardId = cardId;
        this.money = momey;
    }

    public Account() {
    }
}

2.创建一个任务类(DrawThread)

public class DrawTread extends Thread{

    private Account acc;

    public DrawTread(Account acc, String name) {
        super(name);
        this.acc = acc;
    }

    @Override
    public void run() {
        //取钱(小明,小红)
        acc.drawMoney(100000.00);
    }
}

3.测试(ThreadTest)

public class ThreadTest {
    //模拟线程安全问题
    public static void main(String[] args) {
        //银行账里有10万元,小明和小红同事来取钱(取10万)
        Account acc = new Account("ZMSN-110",100000.00);
        new DrawTread(acc,"小红").start();
        new DrawTread(acc,"小明").start();
    }
}

最后的结果:

小明来取钱,取钱成功!
小红来取钱,取钱成功!
小明来取钱后,账户余额0.0
小红来取钱后,账户余额-100000.0

从这里就可以看出出现了线程安全问题。上面的代码具体的讲解就不再过多赘述,主要是了解线程安全问题是怎么产生的。

二、解决线程安全的办法

线程同步:解决线程安全问题

线程同步的思想:让多个线程先后依次访问公共资源,从而解决问题

 常见方案: 加锁:每次只允许一个线程加锁,加锁后才能访问,访问完毕后自动解锁,然后将其他线程再加锁进来

加锁的方案有三种。下面来介绍三中方案:

方法一:同步代码块

作用:把访问共享资源的核心代码上锁,保证线程安全

synchronized(同步锁){

访问共享资源的核心代码 

} 

同步锁使用的注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug

实例方法建议用  this  作为锁对象 ;静态方法建议用  类名.class  作为锁对象。

以上面的案例为解决对象:

    public static void Test() {
         synchronized (Account.class){
             //静态方法推荐使用  类名.class  来上锁
        }
   }
//小红和小明同时过来
    public void drawMoney(double money){
        //先判断谁来取钱
        String name = Thread.currentThread().getName();
        //判断余额是否充足
        //this正好代表共享资源,这里最好不要用字符串,因为字符串在常量池中只有一份,会锁住其他的人,比如小明一家来取钱会锁住小黑一家人
        //synchronized ("字符串") {}最好不要用
        synchronized (this) {
            if (this.money >= money){
                System.out.println(name + "来取钱,取钱成功!");
                this.money -= money;
                System.out.println(name + "来取钱后,账户余额" + this.money);
            }else{
                System.out.println(name + "来取钱,余额不足!");
            }
        }
    }

上面如果用字符串,则会锁住小黑一家人,当小明一家人正在取钱时,小黑一家人是不能取钱的,会出现问题的,建议用this

  public static void main(String[] args) {
        //银行账里有10万元,小明和小红同事来取钱(取10万)
        Account acc = new Account("ZMSN-110",100000.00);
        new DrawTread(acc,"小红").start();
        new DrawTread(acc,"小明").start();

        //另一家人
      //  Account acc1 = new Account("ZMSN-111",100000.00);
        //new DrawTread(acc1,"小黑").start();
       // new DrawTread(acc1,"小白").start();
}

方法二:同步方法

作用:把访问共享资源的方法给上锁,保证线程安全

修饰符 synchronized 返回值类型 方法名称(参数列表){

共享资源的代码 

}

底层原理:含有隐似的锁对象,只是锁的范围是整个方法的代码

同步方法和同步代码块的优缺点:

范围上:同步代码块锁的范围更小,同步方法锁的范围更大 *

可读性:同步方法更好

//小红和小明同时过来
    public synchronized void drawMoney(double money){
        //加一个synchronized 即可
            
}

方法三:Lock锁

Lock是接口,所以要找他的实现类ReentrantLock来创建对象

private finall Lock l = new ReentrantLock();(这里建议用finally修饰)

解锁的代码放在try-catch-finally的finally{}中。

 public class Account {
    private String cardId;
    private double money;

    private finall Lock l = new ReentrantLock();

    //小红和小明同时过来
    public void drawMoney(double money){
        //先判断谁来取钱
        String name = Thread.currentThread().getName();
        l.lock();加锁
        //判断余额是否充足
            if (this.money >= money){
                System.out.println(name + "来取钱,取钱成功!");
                this.money -= money;
                System.out.println(name + "来取钱后,账户余额" + this.money);
            }else{
                System.out.println(name + "来取钱,余额不足!");
            }
        l.unlock();解锁
       
    }

三种方式的运行结果:

小红来取钱,取钱成功!
小红来取钱后,账户余额0.0
小明来取钱,余额不足!

成功解决问题!

你可能感兴趣的:(java,开发语言,安全)