线程安全及实现原理

一、基本概念
  1. 锁(重量级锁)是什么?
    每个对象实例都有一个monitor(C++实现), synchronize对象锁,其指针指向的是该对象monitor对象的起始地址。
ObjectMonitor {
_count = 0;          // 被获取之后,count + 1
_owner = NULL;   // 获取到锁的线程
_WaitSet = NULL;   // 线程获取锁失败,会被阻塞,进入_WaitSet等待被唤醒
_EntrySet = NULL;  // 多个线程访问同步块,首先进入_EntrySet
}
  1. 锁的状态
    java对象头包括两部分:
  • 一、存储对象自身的运行数据(如hashCode、GC分代年龄等),此部分称为Mark Word,是实现轻量级锁和偏向锁的关键。
  • 二、用于存储指向方法区对象类型数据的指针。


    线程安全及实现原理_第1张图片
    Mark Word.png
二、线程安全的实现方法
  • 互斥同步
    互斥是因,同步是果
    synchronize编译后,会在同步块前后分别形成monitorenter和monitorexit,这俩字节码都需要一个reference类型的参数来指明锁定和解锁的对象。
    如果synchronize明确指定了对象参数,那就是这个对象的reference。如果没指定,那就根据synchronize修饰的是实例方法还是类方法,取相应的对象实例或者Class对象作为锁对象。
    synchronize是可重入的,不会出现自己把自己锁死的情况。
    相比ReentrantLock,二者很相似,目前性能也基本持平,只是ReentrantLock增加了一些高级功能,如**等待可中断、公平锁、绑定多个条件。

  • 非阻塞同步
    互斥同步最主要的问题就是线程阻塞和唤醒带来的性能问题,属于一种悲观锁
    而基于冲突检测的乐观并发策略,如CAS指令,比如JDK中incrementAndGet,就是用for(;;)不断重试,直到设置成功为止。

  • 无同步方案
    线程本地存储(ThreadLocal)
    ThreadLocal: 在每个线程都创建了一个变量副本

public T get() {};  // 获取ThreadLocal 在当前线程保存的变量副本
public void set(T value) {}; // 设置当前线程中的变量副本
public void remove() {};  // 移除当前线程中变量副本
protected T initialValue() {};

实现方式:每个线程Thread内部都有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,此变量是用来保存变量副本的,当前threadLocal对象为key,value为变量值。

public T get()   {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);  // 返回当前线程ThreadLocal.ThreadLocalMap对象
if (null != map)
{
     ThreadLocalMap.Entry entry = map.getEntry(this);  
     // 注意,key是ThreadLocal对象,而非线程对象。
     if(null != entry)
     {
         return (T)entry.getValue();
     }
     return setInitialValue();
}
}

三、锁优化
  • 自旋锁与自适应锁
    对互斥同步性能影响最大是阻塞的实现、挂起和恢复等需要转入内核实现的操作。
    自旋就是忙循环,让线程稍等一会,而不放弃处理器的执行时间。
  • 锁消除
    对不可能存在共享竞争的锁进行消除
  • 轻量级锁


    线程安全及实现原理_第2张图片
    图片1.png

JVM首先在当前线程的帧栈中建立一个名为Lock Record 的空间,用于存储锁对象目前的mark word 拷贝。然后,JVM尝试用CAS操作,将锁对象的Mark word更新为指向Lock Record的指针,如果成功了,那么线程就拥有了该对象锁,mark word状态更新为00,表示此对象处于轻量级所状态。

线程安全及实现原理_第3张图片
图片2.png

  • 偏向锁
    偏向于第一个获取它的线程,如果在接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步
    具体过程和轻量级锁类似,只不过需要保存在mark word中保存偏向线程id,并且标志位为01
四、synchronize 实现原理

jdk1.6之前,synchronize是典型的互斥同步,然而互斥同步最大的问题就是线程的阻塞与唤醒带来的性能问题,1.6之后jdk团队对锁的各项优化也应用到synchronize上面:
锁升级(不能降级)
无状态锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

你可能感兴趣的:(线程安全及实现原理)