并发编程 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++;
	}
}
发布了48 篇原创文章 · 获赞 165 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/FMC_WBL/article/details/97966837