线程安全

多个线程,同时操作同一个共享资源的时候,可能会出现安全问题。

例如:两个人来取钱的案例

 

public class test {
    public static void main(String[] args) {
        // 1. 创建一个账户对象。代表账户人的共享账户。
        Account acc = new Account( "ICBC-110",  100000);
        // 2. 创建两个线程,代表两个代理同时操作该账户,一会取100,一会取200。
        new DrawThread(acc, "小明").start(); // 小明
        new DrawThread(acc, "小红").start(); // 小红
    }
}

线程启动

public class DrawThread extends Thread {
    private Account acc;
    public DrawThread(Account acc, String name) {
        super(name);
        this.acc = acc;
    }
    @Override
    public void run() {
        acc.drawMoney(100000);

    }
}

取钱账号及构造器

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

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

// 小明和小红可以过来
public void drawMoney(double money) {
    // 先知道是谁来取钱
    String name = Thread.currentThread().getName();
    // 1. 判断账户当前余额是否足够
    if (this.money >= money) {
        System.out.println(name + "来取钱成功,吐出:" + money);
        this.money -= money;
        System.out.println(name + "来取钱后,余额剩余:" + this.money);
    } else {
        System.out.println(name + "来取钱,余额不足!");
    }
}

public String getCardId() { return cardId; }
public void setCardId(String cardId) { this.cardId = cardId; }
public double getMoney() { return money; }

}

线程同步

目的:解决线程安全问题。

思想:让多个线程先后一次访问共享资源,这样就解决了安全问题。

常见方案:

加锁

每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能加锁进来。

方法一、同步代码块(synchronized)

把访问共享资源的核心代码给上锁,以此保证线程安全。

this只会影响同一个账户下的用户,不会影响不同用户的账户。

synchronized ("lock") {
    if (this.money >= money) {
        System.out.println(name + "来取钱成功,吐出:" + money);
        this.money -= money;
        System.out.println(name + "来取钱后,余额剩余:" + this.money);
    } else {
        System.out.println(name + "来取钱,余额不足!");
    }
}

ctrl alt t  就可以上锁。

对于静态对象来说

synchronized ("lock") {
    if (this.money >= money) {
        System.out.println(name + "来取钱成功,吐出:" + money);
        this.money -= money;
        System.out.println(name + "来取钱后,余额剩余:" + this.money);
    } else {
        System.out.println(name + "来取钱,余额不足!");
    }
}

方法二、同步方法

作用:把访问资源的和核心方法给上锁,以此保证线程安全。

原理同上

public synchronized void drawMoney(double money) {
    // 先知道是谁来取钱
    String name = Thread.currentThread().getName();
    // 1. 判断账户当前余额是否足够
   
        if (this.money >= money) {
            System.out.println(name + "来取钱成功,吐出:" + money);
            this.money -= money;
            System.out.println(name + "来取钱后,余额剩余:" + this.money);
        } else {
            System.out.println(name + "来取钱,余额不足!");
        }
    
}

底层原理;

同步方法其实也是有隐式锁对象的,只是所得范围是整个方法代码。

如果方法是实例方法,同步方法默认使用this作为锁对象。

如果是静态方法:同步方法默认使用类名.class作为的锁对象。

同步代码块月同步方法比较:

同步代码块锁的范围更小,大家都可以先加载方法,再执行没有被锁的代码,最后分批执行被锁代码,所以效率更高。

方法三、Lock锁

程序员手动上锁,通过他可以创建出锁对象进行加锁和解锁,更强大、灵活、方便。

Lock是接口,不能实例化,要使用他的实现类ReentrantLock来创建Lock锁对象。

public class Account {
    private String cardId;
    private double money;
    private final Lock lk = new ReentrantLock();
public Account(String cardId, double money) {
    this.cardId = cardId;
    this.money = money;
}

//public static void test(){
//    synchronized (Account.class){
//
//    }
//}
// 小明和小红可以过来
public void drawMoney(double money) {
    // 先知道是谁来取钱
    String name = Thread.currentThread().getName();
    // 1. 判断账户当前余额是否足够
    try {
        lk.lock();
        if (this.money >= money) {
            System.out.println(name + "来取钱成功,吐出:" + money);
            this.money -= money;
            System.out.println(name + "来取钱后,余额剩余:" + this.money);
        } else {
            System.out.println(name + "来取钱,余额不足!");
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    } finally {//防止出现错误,一直被锁

        lk.unlock();
    }

}