从JVM的角度看一下Java与线程,内存模型,线程安全以及JVM对于锁的优化
硬件内存模型与JVM内存模型
硬件的效率与一致性:主要是解决处理器与内存读写速度差异过大,所以引入高速缓存解决读写速度问题
硬件的内存数据一致性Java内存模型:Java内存模型规定所有的变量都在主内存中,每条线程还有自己的工作内存,工作内存保存了主内存的变量副本,所有的线程只能访问自己的工作内存,通过工作内存向主内存进行同步来达到线程之间的数据共享
线程与内存的交互关系这里所讲的主内存,工作内存和之前说的Java堆,栈,方法区并不是同一个层次进行内存的划分,这两者基本没关系。如果一定要勉强对应,那么主内存对应Java堆中对象实例部分,工作内存相对于虚拟机栈部分区域。
JVM有自己的内存模型,可以看到不同的硬件也有自己的内存设计主要还是针对缓存一致性协议,JVM通过自己的内存模型向上让开发者可以理解并使用而不必关心硬件内存设计。
volatile关键字有2个作用
1保证线程之间的可见性
2禁止指令重排(这条通过内存屏障实现,同时它也确保了Java中单个操作的原子性)
Java与线程
Java线程的实现:Java在1.2之前使用用户线程实现,之后根据实际的os映射到内核线程实现,如果有支持混合就使用混合
1内核线程实现:直接由操作系统内核支持的线程,一般使用内核线程的高级接口(轻量级进程)它就是我们一般所讲的线程,需要在用户态和内核态之间来回切换。
内核线程实现2用户线程实现:用户线程的建立,同步,销毁和调度完全在用户态中完成不需要内核的帮助,实现起来较复杂,问题较多,比如“阻塞如何处理”,“多处理器系统如何将线程映射到其他处理器上”等。优点就是完全自我实现,自控,不受实际os影响
用户线程实现3用户线程加轻量级进程混合实现
混合实现Java线程调度采用抢占式线程调度,也就是交给os进行调度。
与此同时还有协同式线程调度,就是自己控制线程返回,实现简单,但是问题很大,如果一个线程出了问题很容易引起系统崩溃。
状态转换
Java线程中有5种状态
1新建:创建后尚未启动
2运行:运行或者等待cpu分配时间
3等待:wait
4阻塞:竞争排他锁
5结束:线程结束了
线程安全
线程安全通俗的理解:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的
** Java中操作共享数据的线程安全等级
1不可变:final属性只要没有this逃逸问题,那么各个线程看到的都是绝对安全的
2绝对线程安全:不管如何使用都会达到线程安全,Java中绝大部分线程安全API都不是绝对线程安全的
3相对线程安全:只在对象单独操作时是线程安全的,Java中绝大部分线程安全API都是相对线程安全的
4线程兼容:对象本身并不是线程安全的,但是可以通过调用方正确的使用同步达到线程安全,JavaAPI中大部分类都属于
5线程对立:无论如何都无法做到线程安全,Java中很少出现
实现线程安全
1互斥同步:synchronized或juc实现
2非阻塞同步:一般的CAS操作
3不同步:可重入代码(代码逻辑只依赖输入),本地线程对象
实际上这三个情况用的都非常多,但是现在很多服务器端应用都必须要集群防止单点故障,那么实际上单台机器的并发就被限制了,不过一般都是通过数据库或者redis这样的可以支持CAS操作或者锁操作的其他中间件进行转移了,总体的思想是不变的。
锁优化
1自旋锁和自适应锁:在进行锁之前适当的自旋以避免进行锁操作(避免切换线程所带来的资源浪费),自适应就是会自行判断并调整自旋等待时长
2锁消除:对于不存在竞争的数据访问会消除优化掉锁操作
3锁粗化:避免在一个线程中返回对一个对象进行加锁,释放锁操作,那么会进行锁粗化操作
4轻量级锁:在对象竞争锁时先进行一次CAS操作如果成功了,那么就不需要进行加锁了,也就是用CAS替代锁了,这个前提是“绝大部分的锁在整个同步周期内都是不存在竞争的”,如果存在锁竞争,那么轻量级锁就膨胀到重量级锁
5偏向锁:这个锁会偏向第一个获得它的线程,当第一个线程获得这个锁时,那么它后续进行所有的操作都不需要进行同步,当其他线程常识获取这个锁时,偏向模式结束,进入轻量级锁,这个锁的前提条件是“带有同步,但是没有竞争”
几种锁的状态转移每种优化都是针对不同前提情况的优化,如果打破了前提那么就会进行锁状态转移,最终的状态就是加锁。这些优化都可以针对实际情况通过JVM参数进行调整。可以看到大部分的前提都是没有竞争,或者大部分时候没有竞争,如果线上数据明确线程竞争激烈,那么不如直接关闭这些优化反而能提升性能。
总结
从JVM的角度看了一下Java并发,实际的开发中设计到并发竞争的更多的是在框架层,大部分时候都通过新建对象进行了隔离,大部分都是功能对象实例需要考虑多线程的数据并发问题。很多业务功能中更喜欢用本地线程进行数据隔离。
网友评论