Java 并发 01

并发编程的幕后故事:

  1. CPU、内存、I/O设备都在不断迭代,不断朝更快的方向努力。但是,在这个快速发展的过程中,有一个核心矛盾一直存在,就是三者的速度差异,假设CPU执行一条普通指令需要一天,那么内存读写内存得等待一年。内存和I/O设备的的速度差异就更大了,内存一天,I/O设备是地上十年
  2. 根据木桶理论,程序整体性能取决于最慢的操作-读写I/O设备,也就是说单方面提高CPU性能是无效的。
  3. 为了平衡三者,计算机体系机构、操作系统、编译程序都做了贡献,主要体现为:
    CPU增加了缓存,以均衡内存的速度差异;
    操作系统增加了进程、线程,以分时复用CPU,进而均衡CPU与I/O设备的速度差异;
    编译程序优化指令执行次序,使得缓存能够更加合理地利用。

源头之一:缓存导致的可见性问题
一个线程对共享变量的修改,另外一个线程能够立刻看到,我们称为有效性。

public class Test {
  private static long count = 0;
  private void add10K() {
    int idx = 0;
    while(idx++ < 10000) {
      count += 1;
    }
  }
  public static long calc() {
    final Test test = new Test();
    // 创建两个线程,执行 add() 操作
    Thread th1 = new Thread(()->{
      test.add10K();
    });
    Thread th2 = new Thread(()->{
      test.add10K();
    });
    // 启动两个线程
    th1.start();
    th2.start();
    // 等待两个线程执行结束
    th1.join();
    th2.join();
    return count;
  }
}
//运行结果随机:10000~20000之间

在这里插入图片描述

我们假设线程A和线程B同时开始执行,那么第一次都会将Count=0读到各自的CPU里,执行完count+1之后,各自CPU缓存里的值都是1,同时写入内存后,我们会发现内存中是1,而不是我们期望的2。之后由于各自的CPU缓存都有了count的值都是小于2000的。这就是缓存可见性问题
源头之二:线程切换带来的原子性问题
由于I/O太慢,早期的操作系统就发明了多线程,即使在单核的CPU上我们也可以一边听着歌,一边写代码,这都是多线程的功劳
操作系统允许某个进程执行一小片时间,例如五十毫秒,过了五十毫秒系统就会重新选择进程来执行我们称为“任务切换”,这五十毫秒称为“时间片”。
在这里插入图片描述

在上面代码中Count+=1至少需要三条cpu指令
指令1:首先,需要把变量count从内存加载到CPU的寄存器;
指令2:之后,在寄存器中执行+1操作;
指令3:最后,将结果写入内存(缓存机制导致可能写入的是CPU缓存而不是内存)。
在这里插入图片描述
我们把一个或者多个操作在CPU执行的过程中不被中断的特性称为原子性。
源头之三:编译带来的有序问题
经典的单例模式:

public  class  Singleton{
     static Singleton  instance;
     static  Singleton  getInstance(){
			if(instance==null){
   				synchronized(Singleton.class){
      				 if(instance==null){
          		 		instance=new  Singleton();
  					}
  				}	
  		}	
 }

我们认为new操作应该是:

  • 分配一块内存M

  • 再内存上初始化Singletion变量

  • 然后M的地址赋值给instance变量
    但实际上路径是这样的:

  • 分配一块内存M

  • 将M的地址赋值给Instance变量

  • 最后在内存M上初始化Singleton对象

优化后导致什么问题呢?我们假设线程A先执行getInstance()方法,当执行完指令2时恰好发生了线程切换,切换到了线程B上;如果此时线程B也执行getInstance()方法,那么线程B在执行第一个判断时会发现instance!=null,所以直接返回instance,而此时的instance是没有初始化过的,如果我们这个时候访问instance的成员变量就可能触发空指针异常
在这里插入图片描述

总结
只要我们能够深刻理解可见性、原子性、有序性在并发场景下的原理,很多并发bug都是可以理解、可以诊断的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值