线程安全与同步

线程安全问题是什么

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

取钱模型演示

需求:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元。 如果小明和小红同时来取钱,而且2人都要取钱10万元,可能出现什么问题呢?

发生的原因

多个线程同时修改同一个共享资源。

线程安全问题案例模拟

需求: 小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,模拟2人同时去取钱10万。

分析:

①:需要提供一个账户类,创建一个账户对象代表2个人的共享账户。

②:需要定义一个线程类,线程类可以处理账户对象。

③:创建2个线程对象,传入同一个账户对象。

④:启动2个线程,去同一个账户对象中取钱10万。

代码:

public class Account {
    private String cardId;
    private double money; // 账户的余额

    public Account(){

    }

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

    /**
       小明 小红
     */
    public void drawMoney(double money) {
        // 0、先获取是谁来取钱,线程的名字就是人名
        String name = Thread.currentThread().getName();
        // 1、判断账户是否够钱
        if(this.money >= money){
            // 2、取钱
            System.out.println(name + "来取钱成功,吐出:" + money);
            // 3、更新余额
            this.money -= money;
            System.out.println(name + "取钱后剩余:" + this.money);
        }else {
            // 4、余额不足
            System.out.println(name +"来取钱,余额不足!");
        }

    }

    public String getCardId() {
        return cardId;
    }

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

    public double getMoney() {
        return money;
    }

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

}
/**
   取钱的线程类
 */
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 ThreadDemo {
    public static void main(String[] args) {
        // 1、定义线程类,创建一个共享的账户对象
        Account acc = new Account("ICBC-111", 100000);

        // 2、创建2个线程对象,代表小明和小红同时进来了。
        new DrawThread(acc, "小明").start();
        new DrawThread(acc, "小红").start();
    }
}

同步思想概述

线程同步是为了解决线程安全问题。

1、取钱案例出现问题的原因?

多个线程同时执行,发现账户都是够钱的。

2、如何才能保证线程安全呢?

让多个线程实现先后依次访问共享资源,这样就解决了安全问题。

线程同步的核心思想

加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。

方式一:同步代码块

作用:把出现线程安全问题的核心代码给上锁。

原理:每次只能一个线程获得锁从而能进入代码,执行完毕后自动解锁,其他线程才可以进来执行。

格式:

synchronized(同步锁对象) {
	操作共享资源的代码(核心代码)
}

锁对象要求

理论上:锁对象只要对于当前同时执行的线程来说是同一个对象即可。但是锁对象用任意唯一的对象不好,会影响其他无关线程的执行。

规范上:建议使用共享资源作为锁对象。 对于实例方法建议使用this作为锁对象。 对于静态方法建议使用字节码(类名.class)对象作为锁对象。

示例

/**
    账户类:余额,卡号
 */
public class Account {
    private String cardId;
    private double money; // 余额 关键信息

    public Account() {
    }

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

    public String getCardId() {
        return cardId;
    }

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

    public double getMoney() {
        return money;
    }

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

//    // 100个线程人
//    public static void run(){
//        synchronized (Account.class){
//
//        }
//    }


    /**
      小明 小红
     */
    public void drawMoney(double money) {
        // 1、拿到是谁来取钱
        String name = Thread.currentThread().getName();
        // 同步代码块
        // 小明 小红
        // this == acc 共享账户
        synchronized (this) {
            // 2、判断余额是否足够
            if(this.money >= money){
                // 钱够了
                System.out.println(name+"来取钱,吐出:" + money);
                // 更新余额
                this.money -= money;
                System.out.println(name+"取钱后,余额剩余:" + this.money);
            }else{
                // 3、余额不足
                System.out.println(name+"来取钱,余额不足!");
            }
        }
    }
}

方式二:同步方法

作用:把出现线程安全问题的核心方法给上锁。

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

格式

修饰符 synchronized 返回值类型 方法名称(形参列表) {
	操作共享资源的代码
}

同步方法底层原理

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

如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!

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

是同步代码块好还是同步方法好一点?

同步代码块锁的范围更小,同步方法锁的范围更大。 所以从性能上说是同步代码块好点,这点好在现在的硬件条件下可能也微不足道,所以可读性更好的同步方法会使用得多点。

示例

    public synchronized void drawMoney(double money) {
        // 1、拿到是谁来取钱
        String name = Thread.currentThread().getName();
        // 2、判断余额是否足够
        // 小明  小红
        if(this.money >= money){
            // 钱够了
            System.out.println(name+"来取钱,吐出:" + money);
            // 更新余额
            this.money -= money;
            System.out.println(name+"取钱后,余额剩余:" + this.money);
        }else{
            // 3、余额不足
            System.out.println(name+"来取钱,余额不足!");
        }
    }

方式三:Lock锁

为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。

Lock实现提供比使用synchronized方法和语句块可以获得更广泛地锁定操作。 

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值