作者:Matrix海子
出处:http://www.cnblogs.com/dolphin0520/
一、内存模型相关概念
由于CPU处理速度比主存读写速度差异很大,大大降低指令执行速度
== 出现了CPU高速缓存
读取:主存 ==》 高速缓存 ==》 CPU
遇上多线程后,出现了缓存不一致现象
各个线程都有自己单独的高速缓存,在对主存的读写上,就出现了不一致的情况
解决方法:(硬件层面)
在总线加LOCK锁 == 阻塞了其他CPU对其他部件的访问(如内存) == 只有一个CPU能访问 ==》问题:锁住总线期间,其他CPU无法访问内存,效率低下
缓存一致协议(最出名 Intel 的MESI协议) CPU协数据时,发现操作的是共享变量(其他CPU有该变量副本),会通知其他CPU该变量缓存行置为无效,当其他CPU要读取变量时,发现无效,会重新从内存读取
二、并发编程中的三个概念
原子性:一个操作或者多个操作 要么都执行,要么都不执行
可见性:多个线程访问同一变量,一个线程修改了变量的值,其他线程能立刻看到修改的值
有序性:程序执行顺序按照代码先后顺序执行。 == 处理器可能会进行指令重排序 == 单线程不受影响,会考虑按照指令间的数据依赖性。但是多线程
三、Java内存模型
Java内存模型规定所有变量都是存在主存当中,每个线程有自己的工作内存,线程对变量的操作都必须在工作内存中,不能直接操作主存,也不能操作其他线程的工作内存。
原子性:只有简单的读取、赋值(a=1)是原子操作
lock、synchronized 保证任一时刻只有一个线程执行该代码块。
可见性:
volatile 1、一个线程修改了某变量,其他线程立即可见、2、禁止指令重排序
synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。
有序性:
在Java里面,可以通过volatile关键字来保证一定的“有序性”(具体原理在下一节讲述)。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
四、深入剖析volatile关键字
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
是原子性吗
对于非原子操作,不能保证
public class Test {
public int inc = 0;
public synchronized void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
public class Test {
public int inc = 0;
Lock lock = new ReentrantLock();
public void increase() {
lock.lock();
try {
inc++;
} finally{
lock.unlock();
}
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
public class Test {
public AtomicInteger inc = new AtomicInteger();
public void increase() {
inc.getAndIncrement();
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,前面的肯定全部执行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
4.volatile的原理和实现机制
前面讲述了源于volatile关键字的一些使用,下面我们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。
下面这段话摘自《深入理解Java虚拟机》:
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
五、使用
条件:
对变量的写操作不依赖当前值 比如a++ 就不行
该变量没有包含在具有其他变量的不变式中
== 总之要保证原子性
场景:标记量处理 、double check