一、开始前先普及下基础知识
1、多核cpu,每颗cpu上都有缓存。线程不在同一个核上执行,会出现变量修改可见性问题,最后出现cpu缓存与内存的数据不一致。
2、速度:cpu>内存>io,为了充分利用cpu,不被io速度限制,操作系统使用多进程来实现边听歌边聊QQ--每50毫秒选择一个进程来执行(任务切换),50毫秒称为时间片。现在操作系统都是基于更轻量的线程调度,现在的任务切换即线程切换。
3、cpu都是基于指令来执行的,不是高级语言的一条语句,一条语句往往都是有多条指令来完成的。比如执行n = n+1 ,三条指令 (1)n从内存到寄存器(2)寄存器执行n+1(3)将结果写入内存/cpu缓存。
4、编译器会为了优化性能,改变语言的编译顺序,比如先给你初始化内存空间,再进行其他操作。
二、并发问题的出现
1、并发程序都是基于多线程的,由于多核cpu缓存可见性问题,不同线程在操作一共享变量时就会出现结果的不一致情况。
2、假设在单核cpu上根据时间片进行线程切换,当线程一组指令执行一半的时候切换另一线程执行另一组命令,这时就会出现原子性问题。如果两条线程同时执行n = n+1这一组命令,A线程将n=0命令加载到寄存器这时线程切换B线程执行完全部命令将结果1更新到内存,接着A线程继续执行再将结果1更新到内存。
3、编译优化造成的问题,根据语句的执行顺序是123、编译过后可能变成213。比如A线程执行这组命令先去分配内存地址给对象,此时对象成员变量还没初始化。这时线程切换另一线程也进来执行发现对象非null直接返回,造成使用对象成员变量空指针异常。
三、并发问题解决
1、通过语言提供的保证可见性的关键之修饰变量使得每个线程操作结果可见即让cpu缓存失效。
2、通过同步的方式使变量同一时间只能一条线程访问保证原子性(会堵塞)
3、乐观锁,有3个操作数,内存值V,旧的值A,要修改的新值B。当内存的值和旧的值一致允许线程修改成B值,否则放弃修改重新尝试。