说说你对
JMM
内存模型的理解?为什么需要
JMM?说说
CyclicBarrier
和
CountDownLatch
的区别?
随着
CPU
和内存的发展速度差异的问题,导致
CPU
的速度远快于内存,所以现在的
CPU
加入了高速
缓存,高速缓存一般可以分为
L1
、
L2
、
L3
三级缓存。基于上面的例子我们知道了这导致了缓存一致
性的问题,所以加入了缓存一致性协议,同时导致了内存可见性的问题,而编译器和
CPU
的重排序
导致了原子性和有序性的问题,
JMM
内存模型正是对多线程操作下的一系列规范约束,因为不可能
让陈雇员的代码去兼容所有的
CPU
,通过
JMM
我们才屏蔽了不同硬件和操作系统内存的访问差异,
这样保证了
Java
程序在不同的平台下达到一致的内存访问效果,同时也是保证在高效并发的时候程
序能够正确执行。
原子性
:
Java
内存模型通过
read
、
load
、
assign
、
use
、
store
、
write
来保证原子性操作,此外还有
lock
和
unlock
,直接对应着
synchronized
关键字的
monitorenter
和
monitorexit
字节码指令。
可见性
:可见性的问题在上面的回答已经说过,
Java
保证可见性可以认为通过
volatile
、
synchronized
、
final
来实现。
有序性
:由于处理器和编译器的重排序导致的有序性问题,
Java
通过
volatile
、
synchronized
来保
证。
happen-before
规则
虽然指令重排提高了并发的性能,但是
Java
虚拟机会对指令重排做出一些规则限制,并不能让所有
的指令都随意的改变执行位置,主要有以下几点:
1.
单线程每个操作,
happen-before
于该线程中任意后续操作
2. volatile
写
happen-before
与后续对这个变量的读
3. synchronized
解锁
happen-before
后续对这个锁的加锁
4. final
变量的写
happen-before
于
final
域对象的读,
happen-before
后续对
final
变量的读
5.
传递性规则,
A
先于
B
,
B
先于
C
,那么
A
一定先于
C
发生
说了半天,到底工作内存和主内存是什么?
主内存可以认为就是物理内存,
Java
内存模型中实际就是虚拟机内存的一部分。而工作内存就是
CPU
缓存,他有可能是寄存器也有可能是
L1\L2\L3
缓存,都是有可能的。
多线程有什么用?
一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这
个回答更扯淡。所谓
"
知其然知其所以然
"
,
"
会用
"
只是
"
知其然
"
,
"
为什么用
"
才是
"
知其所以然
"
,只
有达到
"
知其然知其所以然
"
的程度才可以说是把一个知识点运用自如。
OK
,下面说说我对这个问题
的看法:
(
1
)发挥多核
CPU
的优势
随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,
4
核、
8
核甚至
16
核的也都不少见,如果是单线程的程序,那么在双核
CPU
上就浪费了
50%
,在
4
核
CPU
上就浪费
了
75%
。
单核
CPU
上所谓的
"
多线程
"
那是假的多线程,同一时间处理器只会处理一段逻辑,只不过
线程之间切换得比较快,看着像多个线程
"
同时
"
运行罢了
。多核
CPU
上的多线程才是真正的多线
程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核
CPU
的优势来,达到充分利用
CPU
的目的。
(
2
)防止阻塞
从程序运行效率的角度来看,单核
CPU
不但不会发挥出多线程的优势,反而会因为在单核
CPU
上运
行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核
CPU
我们还是要应用多线程,
就是为了防止阻塞。试想,如果单核
CPU
使用单线程,那么只要这个线程阻塞了,比方说远程读取
某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止
运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,
也不会影响其它任务的执行。
(
3
)便于建模
这是另外一个没有这么明显的优点了。假设有一个大的任务
A
,单线程编程,那么就要考虑很多,
建立整个程序模型比较麻烦。但是如果把这个大的任务
A
分解成几个小任务,任务
B
、任务
C
、任务
D
,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。
说说
CyclicBarrier
和
CountDownLatch
的区别?
两个看上去有点像的类,都在
java.util.concurrent
下,都可以用来表示代码运行到某个点上,二者
的区别在于:
(
1
)
CyclicBarrier
的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了
这个点,所有线程才重新运行;
CountDownLatch
则不是,某线程运行到某个点上之后,只是给某
个数值
-1
而已,该线程继续运行
(
2
)
CyclicBarrier
只能唤起一个任务,
CountDownLatch
可以唤起多个任务
(
3
)
CyclicBarrier
可重用,
CountDownLatch
不可重用,计数值为
0
该
CountDownLatch
就不可再
用了