并发

1、缓存一致性问题

  解决缓存不一致性问题:

1) 通过在总线加 LOCK#锁的方式。(在锁住总线期间,其他 CPU 无法访问 内存,导致效率低下。)

2) 通过缓存一致性协议。该协议保证了每个缓存中使用的共享变量的副本是一致的:当 CPU 向内存写入数据时,如果发现操作 的变量是共享变量,即在其他 CPU 中也存在该变量的副本,会发出信号通知其他 CPU 将该变量的缓存行置为无效状态,因此当其他 CPU 需要读取这个变量时, 发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

volatile关键字

首先明确:

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。

在 Java 中 volatile、synchronized 和 final 实现可见性。

volatile 解决了 线程间共享变量的可见性问题

指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理

-------------------------------------------------------------------------------------------------------------------

一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰 之后,那么就具备了两层内存语义/两个特性

1)保证了不同线程对这个变量进行读取时的可见性,即一个线程修改 了某个变量的值,这新值对其他线程来说是立即可见的。当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存来完成。

2)禁止进行指令重排序,阻止编译器对代码的优化

I)当程序执行到 volatile 变量的读操作或者写操作时,在其前面的操作的

更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定

还没有进行;

II) 在进行指令优化时,不能把 volatile 变量前面的语句放在其后面执行,

也不能把 volatile 变量后面的语句放到其前面执行。

      为了实现 volatile 的内存语义,加入 volatile 关键字时,会在指令序列中插入内存屏障,会多出一个 lock 前缀指令。内存屏障是一组处理器指令,解决禁止指令重排序和内存可见性的问题。编译器和 CPU 可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。处理器在进行重排序时是会考虑指令之间 的数据依赖性。编译器在生成字节码时, 只有一个CPU访问内存时,并不需要内存屏障。

性能

volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行

内存屏障

作用:1.先于这个内存屏障的指令必须先执行,后于这个内存屏障的指令必须后执行。

        2.使得内存可见性。

如果你的字段是 volatile,在读指令前插入 读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据。在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存。

lock 前缀指令在多核处理器下会引发了两件事情:

1、CPU为了提高处理性能,并不直接和内存进行通信,而是将内存的数据读取到内部缓存(L1,L2)再、进行操作,但操作完并不能确定何时写回到内存,如果对volatile变量进行写操作,当CPU执行到Lock前缀指令时,会将这个变量所在缓存行的数据写回到内存,不过还是存在一个问题,就算内存的数据是最新的,其它CPU缓存的还是旧值,所以为了保证各个CPU的缓存一致性,此时实现了缓存一致性协议,每个CPU通过嗅探在总线上传播的数据来检查自己缓存的数据有效性,当发现自己缓存行对应的内存地址的数据被修改,就会将该缓存行设置成无效状态,当CPU读取该变量时,发现所在的缓存行被设置为无效,就会重新从内存中读取数据到缓存中。

2.它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面 的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全 部完成。

分类:LoadLoad 屏障、LoadStore 屏障、 StoreStore屏障、StoreLoad 屏障(开销最大,兼具其它三种内存屏障的功能。);

使用场景:双重校验锁 DCL

Java内存模型

Java 内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内 存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作,并且每个线程不能访问其他线程的工作内存。

判断线程安全:先行发生原则

它是判断数据是否存在竞争、线程是否安全的主要依据。先行发生不代表线程安全,先行发生只是用于辅助判定线程安全性

同步容器

Vector:Vector是线程安全的集合类,ArrayList并不是线程安全的类。Vector类对集合的元素操作时都加了synchronized,保证线程安全。

Stack

HashTable:HashTable进行了同步处理,而HashMap没有。

Collections.synchronized方法生成,即Collections 类中提供的静态工厂方法创建的类。

缺陷 :

1.性能问题。2.ConcurrentModificationException 异常。对Vector、ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常:异常原因及解决方案


并发容器

ConcurrentHashMap:线程安全的HashMap的实现

CopyOnWriteArrayList:线程安全且在读操作时无锁的ArrayList

CopyOnWriteArraySet:基于CopyOnWriteArrayList,不添加重复元素

ArrayBlockingQueue:基于数组、先进先出、线程安全,可实现指定时间的阻塞读写,并且容量可以限制

LinkedBlockingQueue:基于链表实现,读写各用一把锁,在高并发读写操作都多的情况下,性能优于ArrayBlockingQueue

ConcurrentHashMap

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容