并发编程 Java并发机制的底层实现原理

Posted 小明学习心得

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程 Java并发机制的底层实现原理相关的知识,希望对你有一定的参考价值。

volatile原理

volatile是轻量级的synchronized,在多处理器开发中保证了共享变量的"可见性",volatile是一个轻量级的synchronized,在多CPU开发中保证了共享变量的“可见性”,也就是说当一个线程修改一个共享变量的时候,另一个线程能够读取到所修改的值。如果volatile使用恰当的话,它将比synchronized的使用和执行成本更低,不会引起上下文的切换和调度。
如果一个变量被声明为volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。 volatile修饰的共享变量在转换为汇编语言后,会出现Lock前缀指令,该指令在多核处理器下引发了两件事:
1、将当前处理器缓存行(CPU cache中可以分配的最小存储单位)的数据写回到系统内存。
2、这个写回内存的操作使得其他CPU里缓存了该内存地址的数据无效。 为了提高处理速度,CPU不直接和内存通信,而是将内存数据读取到cache后进行操作,但何时写回到内存是不确定的。如果线程volatile变量进行了写操作,则JVM会向CPU发送一条Lock前缀指令,将该变量的所在的cache行的数据写回到内存中。同时,为了保证其他CPU所读取到的cache值是一致的,就户实现cache一致性协议,每个CPU通过嗅探在总线上传播的数据来检查自己所缓存的值是否过期。如果CPU发现自己cache行中所对应的内存地址被修改,就会将该cache行设置为无效,从而在对该数据进行修改的时候重新从内存中读取。
volatile的两条实现原则:
1、Lock前缀指令会引起CPU cache(处理器缓存)写回到内存。
2、一个CPU的cache(缓存)写回到内存会导致其他处理器缓存无效。

如何优化volatile

用一种追加字节的方式来优化,追加字节填满缓冲行(Linked-TransferQueue),大多处理器告诉缓存行是64字节。试图修改缓存行数据时,会锁定缓存行。导致其他处理器不能访问锁定的节点。
什么时候不能追加到64字节
缓存行为32字节的cpu时
共享变量不会被频繁写入
ps:java7会淘汰或重新排列无用字段,不能单独使用Object对象来填充

synchronized原理

**synchronized具体表现为三种状况:**
对于普通同步方法,锁是当前实例对象
对于静态同步方法,锁是当前类的Class对象
对于同步方法块,锁是synchronized括号里面配置的对象

**synchronized 实现原理:**
JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者实现细节不同。
代码块同步:使用monitorenter monitorexit 指令实现。monitorenter指令在编译后插入到同步代码块的开始位置,monitorexit指令插入到方法结束和异常处。
方法同步:依靠的是方法修饰符上的 ACC_SYNCHRONIZED 实现。(网上资料)

**Java对象头:**
synchronized 用的锁是存在Java对象头里的。
锁的升级与对比:
锁的四种状态:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。
锁可以升级但不能降级,目的是为了提高获得锁和释放锁的效率。(如何提高的?)

**偏向锁:**
引入原因:大多数情况下,不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让获得锁的代价更低而引入偏向锁。
实现方式:当一个线程访问同步代码块并获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程ID,以后该线程进入和退出同步代码块时不需要进行CAS操作来加锁和解锁,只需简单测试一下对象头的MarkWord里是否存储指向当前线程的偏向锁。

**偏向锁的进入:**
判断是否存在指向当前线程的偏向锁:
存在,直接进入同步代码块。(表示当前线程持有锁)
不存在,且偏向锁标识为0,使用CAS竞争锁。(当前为无锁状态)
不存在,且偏向锁标识为1,尝试使用CAS将对象头的偏向锁指向当前线程。(其他线程持有锁,触发持有锁的线程执行偏向锁撤销操作)

**偏向锁的撤销:**
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
偏向锁的撤销需要等待全局安全点(在这个时间点上没有正在执行的代码)。

**轻量级锁:**
轻量级锁加锁
1、在当前线程栈帧中创建存储锁记录的空间,并将对象头的Mark Word 复制到锁记录中。
2、线程尝试使用CAS将对象头中的Mark Word 替换为指向锁记录的指针。
如果成功:当前线程获得锁。
如果失败:尝试使用自旋获取锁。

**轻量级锁解锁**
轻量级锁解锁时,使用CAS操作将Displaced Mark Word 替换到对象头。
如果成功:表示没有竞争。
如果失败:表示当前存在竞争,锁膨胀为重量级锁。

锁的优缺点对比

锁 优点 缺点 适用场景
偏向锁 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 适用于只有一个线程访问同步块场景
轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度 如果始终得不到锁竞争的线程,使用自旋会消耗CPU 追求响应时间,同步块执行速度非常快
重量级锁 线程竞争不使用自旋,不会消耗CPU 线程阻塞,响应时间缓慢 追求吞吐量,同步块执行速度较长。


优点

缺点

使用场景

偏向锁

加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距

如果线程间存在锁竞争,会带来额外的锁撤销的消耗

适用于只有一个线程访问同步块场景

轻量级锁

竞争的线程不会阻塞,提高了程序的响应速度

如果始终得不到锁竞争的线程,使用自旋会消耗CPU

追求响应时间,同步块执行速度非常快

重量级锁

线程竞争不使用自旋,不会消耗CPU

线程阻塞,响应时间缓慢

追求吞吐量,同步块执行速度较长。

原子操作CAS原理

CAS的原理:CAS操作需要输入两个数值,一个旧值(期望操作之前的值)和一个新值,在操作期间先比较旧值有没有发送变化,如果没有发生变化,才交换新值,发生了变化则不交换
CAS(Compare And Swap),指令级别保证这是一个原子操作
三个运算符: 一个内存地址V,一个期望的值A,一个新值B
基本思路:如果地址V上的值和期望的值A相等,就给地址V赋给新值B,如果不是,不做任何操作。
循环(死循环,自旋)里不断的进行CAS操作

CAS三大问题

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class testCAS
/**
* jdk并发包提供一些类支持原子操作 AtomicInteger AtomicBoolean AtomicLong
* 1.AtomicStampedReference增加版本号,可解决ABA问题
* 2.JVM提供的pause指令 可延迟流水线执行指令,避免内存顺序冲突,可解决循环时间长开销大问题
* 3.AtomicReference类可保证引用对象的原子性,可以把多个变量放在一个对象进行CAS操作,可解决只能保证一个共享变量的原子操作问题
*/
private AtomicInteger atomicI = new AtomicInteger(0);

private int i = 0;

public static void main(String[] args)
final testCAS cas = new testCAS();
List<Thread> ts = new ArrayList<Thread>(600);
long start = System.currentTimeMillis();

for (int j = 0; j < 100; j++)
Thread t = new Thread(new Runnable()
@Override
public void run()
for (int i = 0; i < 10000; i++)
cas.count();
cas.safeCount();


);
ts.add(t);

for (Thread t : ts)
t.start();
// 等待所有线程执行完成
for (Thread t : ts)
try
t.join();
catch (InterruptedException e)
e.printStackTrace();


System.out.println("cas.i:" + cas.i);
System.out.println("cas.atomicI.get():" + cas.atomicI.get());
System.out.println("System.currentTimeMillis() - start:" + (System.currentTimeMillis() - start));


/**
* 使用CAS实现线程安全的计数器
*/
protected void safeCount()
for (;;)
int i = atomicI.get();
boolean suc = atomicI.compareAndSet(i, ++i);
if (suc)
break;



/**
* 非线程安全计数器
*/
protected void count()
i++;


以上是关于并发编程 Java并发机制的底层实现原理的主要内容,如果未能解决你的问题,请参考以下文章

java并发编程艺术学习第二章 java并发机制的底层实现原理 学习记录 volatile

并发编程 Java并发机制的底层实现原理

[并发编程的艺术] 02-Java并发机制的底层实现原理

那些年读过的书《Java并发编程的艺术》并发编程的挑战和并发机制的底层实现原理

Java并发编程:底层实现机制

#yyds干货盘点#Java并发机制的底层实现原理