并发问题是什么
并发问题就是线程不安全,当多线程同时读写一个变量是,因为原子性,缓存可见性,指令重排序等原因,导致变量的实际执行结果和预期不一致
并发问题出现的场景
静态变量,多线程访问类的同一实例
静态变量,多线程访问类的不同实例
实例成员变量,多线程访问同一实例
并发问题产生的原因
(1)线程切换导致原子性问题
进程和线程本质上是增加并行的任务数量来提升CPU的利用率。原子性是指一个操作要么全部执行完毕,要么全部不执行。并发读写同一变量的线程之间任务切换时,如果对变量的读写操作不是原子性的,就会导致并发问题。
(2)多核cpu缓存导致的不一致问题
由于CPU执行速度远远快于内存存取速度,所以为了提高CPU利用率,系统在CPU和内存之间添加了高速缓存cache,cache通常减少IO速度来提高CPU利用率。CPU读取数据时,先访问cache,如果cache命中,就不再访问主存,直接返回cache中的数据。当CPU写数据到内存时,会先写到cache,通常不会马上写回主存,只有cache要被替换或者cache无效时,才会写回主存。
单核CPU不会有不一致问题,这个问题只会出现在多核CPU上,现代处理器大部分都是多核心CPU。
在多核CPU上,每个内核都有自己的独立缓存。当多线程读写同一变量时,如果线程运行在不同内核上,那么它们对同一变量的读写操作就分别在不同的cache中执行。每个cache都是独立的,互相不可见,单个cache中对变量缓存的操作不会影响别的cache,也不知道别的cache中的数据,这样就会导致最终结果的不可控。
(3)指令重排序问题
为提升内核自身执行速度,内核内部使用流水线并行执行多条指令。
指令之间通常因数据相关,名称相关,控制相关造成的依赖关系,这导致流水线中后面的指令需要等待前面的指令完成后才能执行,大大降低了流水线的并行度
为提高流水线并行性能,编译器和内核通常会对指令进行静态和动态调度,在不影响单线程执行结果的前提下,把无关的指令插入到指令空闲的位置提前执行,以提升流水线性能。
解决方法
同步方案:如果线程间需要协作,即一个线程的执行需要依赖其他线程执行的结果,那么就说线程之间是需要同步的,一般添加互斥锁解决
synchronized
Lock
volatile
无同步方案:但是互斥锁会阻塞其他线程的执行,效率较低,所以如果线程间不需要协作,即一个线程的执行不依赖于其他线程的执行结果,这种情况就不需要使用互斥锁的方案。
TheadLocal:当多个线程共同操作一个对象,但是互不影响,不需要同步时,可以不加互斥锁,使用ThreadLocal保证每个线程都有共享对象的一个备份,线程间数据互相隔离,就互不影响。