JUC-八股-01
什么叫进程?什么叫线程?
进程: 简单点讲叫做代码在数据集合上的一次运行活动 。
线程: 进程里面最小的执行单元,简单讲一个线程里面不同的执行路径。
创建线程的方式?
本质只有一种: 通过Thread类进行创建
多种形式:
- 继承Thread类重写run() 方法
- 实现Runnable接口实现run() 方法
- 实现Callable + FutureTask方式
- 交给线程池创建
线程状态
synchronized
访问某一段临界代码或者临界资源的时候需要一把锁,synchronized就是我们最常用的锁。
synchronized 保证原子性,保证可见性
并且他是可重入的
1.它的实现
-
字节码层面
方法 ACC_SYNCHRONIZED
代码块 monitorenter 和 monitorexit指令
public class TestSync { void n() { synchronized (this) { } } public static void main(String[] args) { } }
为什么两次monitorexit呢?第一个是正常退出,第二个是遇到异常自动退出。
-
jvm层面
用c和c++调用操作系统提供的同步机制,不同操作系统不一样。
-
os和硬件
c和c++调用操作系统提供的同步机制, 这些同步机制依赖于硬件
x86 lock cmpxchg xxx 类似cas
2.锁升级的概念
synchronized以前是重量级锁, 因为申请锁资源必须通过kernel, 需要操作系统参与,有点消耗资源。用户态 -> 内核态
用户态->内核态
int 0x80 - 128
保护用户态现场
寄存器压栈
进行syscall
内核态返回eax
恢复用户态现场
用户程序继续执行
后来经过改进有了锁升级的概念
new - 偏向锁 - 轻量级锁 (无锁, 自旋锁,自适应自旋)- 重量级锁
偏向锁:记录这个线程的Id (对象头markword)
自旋锁: 如果线程争用,就升级为自旋锁(线程数量少)
自选锁转 10 次 升级成重量级锁(线程数量多)
为什么有自旋锁还需要重量级锁?
自旋是消耗CPU资源的,如果锁的时间长,或者自旋线程多,CPU会被大量消耗
重量级锁有等待队列,所有拿不到锁的进入等待队列,不需要消耗CPU资源
偏向锁是否一定比自旋锁效率高?
不一定,在明确知道会有多线程竞争的情况下,偏向锁肯定会涉及锁撤销,这时候直接使用自旋锁
JVM启动过程,会有很多线程竞争(明确),所以默认情况启动时不打开偏向锁,过一段儿时间再打开
推荐两篇文章
https://www.jianshu.com/p/b43b7bf5e052
https://www.jianshu.com/p/16c8b3707436
synchronized vs Lock (CAS)
在高争用 高耗时的环境下synchronized效率更高
在低争用 低耗时的环境下CAS效率更高
synchronized到重量级之后是等待队列(不消耗CPU)
CAS(等待期间消耗CPU)
一切以实测为准
Volatile 关键字
使一个变量在多个线程之间可见
保证线程可见性
缓存一致性协议
x86 MESI
禁止指令重排序
不保证原子性: 不能替代synchronized
1. 它的实现
1.字节码层面
ACC_VOLATILE
2.jvm层面
jvm层级的内存屏障
StoreStoreBarrier
volatile写操作
StoreLoadBarrier
LoadLoadBarrier
volatile读操作
LoadStoreBarrier
操作系统级的内存屏障
sfence 它之前的写操作必须必在后面写操作之前完成
lfence ...读...
mfence ...读写..
- os和硬件层面
windows 的lock指令
linux 上实现是 上面一个屏障 下面一个屏障 最后一个lock指令
中间是volatile区域
Java内存模型
https://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html
缓存一致性协议
https://www.cnblogs.com/z00377750/p/9180644.html
双重检查单例模式,我们需要给instance成员变量加上volatile关键字,禁止指令重排序才行。实际上,只有很低版本的Java有这个问题。我们现在用的高版本的Java已经在JDK内部实现中解决了这个问题(解决的方法很简单,只要把对象new操作和初始化操作设计为原子操作,就自然能禁止重排序)。