多线程(三):线程的安全问题

学习内容:

1、 线程的上下文切换
2、 线程的安全(同步)问题
3、 解决线程安全问题方法及代码演示


线程的上下文切换

前提:一个CPU的内核一个时间只能运行一个线程中的一个指令

线程并发:CPU内核会在多个线程间来回切换运行,切换速度非常快,达到同时运行的效果

切换的话就会有三个问题

线程切换回来后,如何从上次执行的指令后执行?

程序计数器(每个线程都有,用于记录上次执行的行数)

线程执行会随时切换,如何保证重要的指令能完全完成?

线程安全问题

CPU进行上下文切换的过程中,性能会降低。
在这里插入图片描述

1、ALU:是能实现多组算术运算和逻辑运算的组合逻辑电路,简称ALU。

2、CU:是Control Unit控制单元,是CPU的一部分,用于执行计算机指令或者Client Unit 监控系统的监控客户端单元的一个计算机学名词

线程的安全(同步)问题

解释:当cpu在线程里来回切换,可能会导致丢失一些操作使整个执行流程缺失步骤,从而会影响到最终的数据问题

那么出现线程安全问题就需要三个条件

  • 多个线程
  • 同一个时间
  • 执行同一段指令或修改同一个变量

下面是一段银行转账案例

/**
 * 银行转账的案例
 */
public class BankDemo {

    //模拟100个银行账户
    private int[] accounts = new int[100];

    {
        //初始化账户
        for (int i = 0; i < accounts.length; i++) {
            accounts[i] = 10000;
        }
    }

    /**
     * 模拟转账
     */
    public void transfer(int from,int to,int money){
        if(accounts[from] < money){
            throw new RuntimeException("余额不足");
        }
        accounts[from] -= money;
        System.out.printf("从%d转出%d%n",from,money);
        accounts[to] += money;
        System.out.printf("向%d转入%d%n",to,money);
        System.out.println("银行总账是:" + getTotal());
    }

    /**
     * 计算总余额
     * @return
     */
    public int getTotal(){
        int sum = 0;
        for (int i = 0; i < accounts.length; i++) {
            sum += accounts[i];
        }
        return sum;
    }

    public static void main(String[] args) {
        BankDemo bank = new BankDemo();
        Random random = new Random();
        //模拟多次转账过程
        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                int from = random.nextInt(100);
                int to = random.nextInt(100);
                int money = random.nextInt(2000);
                bank.transfer(from,to,money);
            }).start();
        }
    }
}

在这里插入图片描述

这里就出现了线程安全的问题,很明显的看出原本银行的总数在中间是发生了变化的。

线程安全问题的解决方法

所以就有了解决这个问题的方法,就是给这段程序上锁,让这段整体代码不受影响执行就上锁,执行完才释放,让其他线程在执行。

总共有三种上锁的方法:

  • 同步方法
  • 同步代码块
  • 同步锁

同步方法

给方法添加synchronized关键字作用是给整个方法上锁当前线程调用方法后,方法上锁,其它线程无法执行,调用结束后,释放锁

    /**
      * @Authoe: smz
      * @Description: 同步方法
      * @Date: 10:58 2021-12-08
      */
    public synchronized void trade(int from,int to,int num){
        if (arr[from]<num){
            throw new RuntimeException("余额不足了哦!");
        }
        //转出的减钱
        arr[from]-=num;
        System.out.println(from+"向"+to+"转出了"+num);
        //转入的加钱
        arr[to]+=num;
        System.out.println(to+"接收了"+from+"的"+num);

    }

同步代码块

任何对象都可以作为锁,对象不能是局部变量

    /**
      * @Authoe: smz
      * @Description: 同步块
      * @Date: 11:15 2021-12-08
      */
    synchronized (this){
        //转出的减钱
        arr[from]-=num;
        System.out.println(from+"向"+to+"转出了"+num);
        //转入的加钱
        arr[to]+=num;
        System.out.println(to+"接收了"+from+"的"+num);
        System.out.println(getTotal());
    }

同步锁

在java.concurrent并发包中的

Lock接口

基本方法:

  • lock() 上锁
  • unlock() 释放锁

常见实现类

  • ReentrantLock 重入锁
  • WriteLock 写锁
  • ReadLock 读锁
  • ReadWriteLock 读写锁

使用方法:

  1. 定义同步锁对象(成员变量)
  2. 上锁
  3. 释放锁
    private Lock lock = new ReentrantLock();

    public void trade(int from,int to,int num){
        lock.lock();
        if (arr[from]<num){
            throw new RuntimeException("余额不足了哦!");
        }
        try {
            //转出的减钱
            arr[from]-=num;
            System.out.println(from+"向"+to+"转出了"+num);
            //转入的加钱
            arr[to]+=num;
            System.out.println(to+"接收了"+from+"的"+num);
            System.out.println(getTotal());
        }finally {
            lock.unlock();
        }

    }

三种锁对比:

  1. 粒度(锁的范围)
    同步代码块/同步锁 < 同步方法
  2. 编程简便
    同步方法 > 同步代码块 > 同步锁
  3. 性能
    同步锁 > 同步代码块 > 同步方法
  4. 功能性/灵活性
    同步锁(有更多方法,可以加条件) > 同步代码块 (可以加条件) > 同步方法

锁对象:

  1. 非静态方法 --> this
  2. 静态方法 —> 当前类.class

synchronized的基本的原理

一旦代码被synchronized包含,JVM会启动监视器(monitor)对这段指令进行监控

线程执行该段代码时,monitor会判断锁对象是否有其它线程持有,如果其它线程持有,当前线程就无法执行,等待锁释放

如果锁没有其它线程持有,当前线程就持有锁,执行代码

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值