文章目录
- 博客概述
- 细节补充
- volatile
- 原子类操作
- 模拟CAS算法
- ConcurrentHashMap
- CountDownLatch
博客概述
本博客针对前面12篇论文,在后续的学习与工作中有所补充。
细节补充
多线程涉及到线程调度,CPU上下文的切换,尽可能利用系统资源。使用不当性能更低。1.5版本之前就是sync关键字没别的了。从1.5版本之后提供了并发包。技巧就多起来了。
volatile
自己理解:
有个线程空间和内存空间的概念,更改了之后,更改的是线程空间的数据,内存空间没改,或者是说,主线程抓取的是一个临时的映射,更改了之后,没有主动去获得新的值。
学习讲解:
内存可见性的问题。jvm会为每一个线程分配一块独立的空间(视频里说的缓存,不准确),用于提高效率。程序运行时,有主存,共享数据在主存里存着,两个线程在操作共享数据,一读一写。
while—true执行的是底层的代码,效率十分高,高到main线程都没机会再从主存中获取数据。
导致持有的flag一直都是false。这就是内存可见性的问题,问题的原因在于:两个问题在操作共享数据时,对数据的操作,彼此不可见。
解决方案除了volatile,还有加锁:同步锁可以解决,保证每次都会给你刷新缓存(线程空间与主存的映射)。
但是,比较麻烦,效率低。保证数据内存可见性的关键字,这里涉及到内存栅栏的概念,不断的同步主存与线程空间里共享数据的实时情况。(可能是触发器,可以这么理解,加了这个关键字,修改(操作数据-读写)直接在主存里面完成)。相比较什么都不加,效率降低点,比锁关键字要高。
原子类操作
对于一个for循环执行十次i++操作就会出现原子性安全问题,i++实际上分为3个操作读-改-写。要解决原子性安全问题,jdk1.5以后在并发包出现了原子类关键字。
CAS算法保证了数据的原子性,cas算法是硬件对于并发操作共享数据的支持。cas操作分为三步骤:
读取内存值V,读取线程空间值A,比较A与V是否相等,然后操作把更新值B赋值给内存值V。cas算法比上锁要效率高。麻烦的是需要写失败的处理代码。
模拟CAS算法
CAS是一种硬件对并发的支持,针对多处理器操作而设计的一种特殊的指令,用于管理对数据的并发访问,它是一种无锁的非阻塞算法的实现。包含了三个操作数内存值V,比较值A,拟写入的值B,比较A与V是否相等,然后操作把更新值B赋值给内存值V,否则不做任何操作。
ConcurrentHashMap
hashmap—>线程不安全的
hashtable----->线程安全的,但是效率低,有锁,锁了整个表,并发访问时,并行变成了串行访问。
复合操作(若存在则删除),可能会出问题,单独的方法都是上锁,复合操作过程中,可能被其他线程抢夺。因为有这种问题的存在,1.5给我们提供了解决并发的包。其中一个工具代表就是ConcurrentHashMap。ConcurrentHashMap采用了锁分段的机制。并发等级有16个,分为16个段。
每个段被称为segment,连接着一个hashmap,后面连接一个链表。并行访问的效率,比hashtable的串行效率高。jdk1.8取消了分段锁,用的是cas来做并发解决。
解决之前存在的线程安全问题,可以使用老式的方法,好处是不用推倒重来。
在介绍一下COW容器,之前的容器,哪怕是线程安全的,在迭代的时候操作数据,会报异常:并发修改异常。1.5之后,我们可以使用cow容器来解决这种遍历时修改的需要。
异常解决。写入并复制的并发集合。当你每次写入时,都会在底层复制一个新列表,再进行添加。
所以添加操作多的时候,不太适合用这个。适合并发迭代操作。
CountDownLatch
闭锁:在完成某些运算时只有其他所有线程的运算全部完成,当前运算才继续执行。
举个例子:比如子线程执行任务,主线程统计时间。可以通过构造函数把闭锁传给子线程,然后主线程用闭锁的await等待子线程全部执行完成。