2021-04-06

作者: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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值