并发编程两个问题
多线程之前有两个问题需要解决:线程之间的通信和线程同步。
线程通信:共享内存和消息传递。共享内存方法是,多个线程通过读写公共内存共享数据,隐式进行线程通信。
线程同步:控制不同线程操作执行的相对顺序
java提供的共享内存方式,隐式进行线程之间的消息传递,但是需要程序员显示提供线程之间的同步。
CPU和缓存一致性
由于现代CPU速度远远超过了内存速度,为了提高CPU访问内存的速度,CPU和内存之间增加高速缓存Cache。当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。随着CPU技术的不断提升,一级高速缓存已经无法满足需求,逐渐发展出多级缓存——L1,L2,L3。L3是L2的缓存,L2是L1的缓存,L1是cpu的缓存。三级缓存从CPU到内存一级比一级容量大,但是访问速度越来越慢。当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。
对于单核CPU只含有一套L1,L2,L3缓存;
单线程。cpu核心的缓存只被一个线程访问。缓存独占,不会出现访问冲突等问题。
单核CPU,多线程。进程中的多个线程会同时访问进程中的共享数据,CPU将某块内存加载到缓存后,不同线程在访问相同的物理地址的时候,都会映射到相同的缓存位置,这样即使发生线程的切换,缓存仍然不会失效。但由于任何时刻只能有一个线程在执行,因此不会出现缓存访问冲突。
多核CPU,多线程。每个核都至少有一个L1 缓存。多个线程访问进程中的某个共享内存,且这多个线程分别在不同的核心上执行,则每个核心都会在各自的cache中保留一份共享内存的缓冲。由于多核是可以并行的,可能会出现多个线程同时写各自的缓存的情况,而各自的cache之间的数据就有可能不同。
上面提到在在CPU和主存之间增加缓存,在多线程场景下会存在缓存一致性问题。除了这种情况,还有一种硬件问题也比较重要。那就是为了使处理器内部的运算单元能够尽量的被充分利用,处理器可能会对输入代码进行乱序执行处理。这就是处理器优化。
除了现在很多流行的处理器会对代码进行优化乱序处理,很多编程语言的编译器也会有类似的优化,比如Java虚拟机的即时编译器(JIT)也会做指令重排。
在CPU和主存之间增加缓存,在多线程场景下就可能存在缓存一致性问题,也就是说,在多核CPU中,每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致。
缓存一致性问题其实就是可见性问题。而处理器优化是可以导致原子性问题的。指令重排即会导致有序性问题。
JAVA内存模型
内存模型
为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。他解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。
Java内存模型
在java中,存放数据的内存主要是分为两块:java对象,类的实例变量,类的静态变量,数组是存放在堆中,堆是java不同线程共享的。java方法中定义的变量,方法参数,异常变量都是存放在java线程栈中,栈是线程独享的。因此JAVA线程直接的通信是通过共享堆内存实现的。也就是说,发生java存储在堆上的数据,都需要考虑多线程访问下的安全问题。
happens-before
JMM提供了happens-before原则来指定线程之间的可见性,happens-before
不是强制规定了程序的执行顺序,只是要求最后的结果应该符合happens-before
原则,程序员可以安装happens-before来实现自己的业务,而不需要担心线程安全问题。
如果多个线程之间的操作超越了happens-before规定的原则,那需要程序员显示进行线程同步控制。
- 程序顺序规则:在一个线程内,每个操作都happens-before后该线程的后序操作
- 监视器规则:对一个锁的解锁,happens-before后面的加锁
- volatile规则:对一个volatile变量的写happens-before对它的读
- 传递性规则:A happens-before B,B happens-before C,那么A happens-before C
- start规则:线程A.start方法的执行happens-before 线程A的所以操作
- join规则:线程B调用线程A.join方法,A线程中所有操作happens-before A.join方法的返回。
网友评论