• 随着多过程多线程的呈现,对共享资源(设施,数据等)的竞争往往会导致资源的应用体现为随机无序
  • 例如:一个线程想在控制台输入"I am fine",刚写到"I am",就被另一线程抢占控制台输入"naughty",导致后果是"I am naughty";对于资源的被抢占应用,咱们能怎么办呢?当然不是凉拌,可应用锁进行同步治理,使得资源在加锁期间,其余线程不可抢占应用

1 锁的分类

  • 乐观锁

    • 乐观锁,每次去申请数据的时候,都认为数据会被抢占更新(乐观的想法);所以每次操作数据时都要先加上锁,其余线程批改数据时就要期待获取锁。实用于写多读少的场景,synchronized就是一种乐观锁
  • 乐观锁

    • 在申请数据时,感觉无人抢占批改。等真正更新数据时,才判断此期间他人有没有批改过(事后读出一个版本号或者更新工夫戳,更新时判断是否变动,没变则期间无人批改);和乐观锁不同的是,期间数据容许其余线程批改
  • 自旋锁

    • 一句话,魔力转转圈。当尝试给资源加锁却被其余线程先锁定时,不是阻塞期待而是循环再次加锁
    • 在锁常被短暂持有的场景下,线程阻塞挂起导致CPU上下文频繁切换,这可用自旋锁解决;但自旋期间它占用CPU空转,因而不实用长时间持有锁的场景

2 synchronized底层原理

  • 代码应用synchronized加锁,在编译之后的字节码是怎么的呢
public class Test {    public static void main(String[] args){        synchronized(Test.class){            System.out.println("hello");        }    }}

截取局部字节码,如下

    4: monitorenter    5: getstatic    #9    // Field java/lang/System.out:Ljava/io/PrintStream;     8: ldc           #15   // String hello    10: invokevirtual #17  // Method java/io/PrintStream.println:(Ljava/lang/String;)V    13: aload_1    14: monitorexit

字节码呈现了4: monitorenter和14: monitorexit两个指令;字面了解就是监督进入,监督退出。能够了解为代码块执行前的加锁,和退出同步时的解锁

  • 那monitorenter和monitorexit,又背着咱们干了啥呢?
  • 执行monitorenter指令时,线程会为锁对象关联一个ObjectMonitor对象
objectMonitor.cpp  ObjectMonitor() {    _header       = NULL;    _count        = 0;   \\用来记录该线程获取锁的次数    _waiters      = 0,    _recursions   = 0;    \\锁的重入次数    _object       = NULL;    _owner        = NULL;  \\以后持有ObjectMonitor的线程    _WaitSet      = NULL;  \\wait()办法调用后的线程期待队列    _WaitSetLock  = 0 ;    _Responsible  = NULL ;    _succ         = NULL ;    _cxq          = NULL ; \\阻塞期待队列    FreeNext      = NULL ;    _EntryList    = NULL ; \\synchronized 进来线程的排队队列    _SpinFreq     = 0 ;    _SpinClock    = 0 ;  \\自旋计算    OwnerIsThread = 0 ;  }
  • 每个线程都有两个ObjectMonitor对象列表,别离为free和used列表,如果以后free列表为空,线程将向全局global list申请调配ObjectMonitor
  • ObjectMonitor的owner、WaitSet、Cxq、EntryList这几个属性比拟要害。WaitSet、Cxq、EntryList的队列元素是包装线程后的对象-ObjectWaiter;而获取owner的线程,既为取得锁的线程
  • monitorenter对应的执行办法
void ATTR ObjectMonitor::enter(TRAPS)  {    ...    //获取锁:cmpxchg_ptr原子操作,尝试将_owner替换为本人,并返回旧值    cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;    ...    // 反复获取锁,次数加1,返回    if (cur == Self) {        _recursions ++ ;        return ;    }    //首次获取锁状况解决    if (Self->is_lock_owned ((address)cur)) {        assert (_recursions == 0, "internal state error");        _recursions = 1 ;        _owner = Self ;        OwnerIsThread = 1 ;        return ;    }    ...    //尝试自旋获取锁    if (Knob_SpinEarly && TrySpin (Self) > 0) {    ...
  • monitorexit对应的执行方void ATTR ObjectMonitor::exit(TRAPS)...代码太长,就不贴了。次要是recursions减1、count缩小1或者如果线程不再持有owner(非重入加锁)则设置owner为null,退锁的持有状态,并唤醒Cxq队列的线程

总结

  • 线程遇到synchronized同步时,先会进入EntryList队列中,而后尝试把owner变量设置为以后线程,同时monitor中的计数器count加1,即取得对象锁。否则通过尝试自旋肯定次数加锁,失败则进入Cxq队列阻塞期待

  • 线程执行结束将开释持有的owner,owner变量复原为null,count自减1,以便其余线程进入获取锁

  • synchronized润饰办法原理也是相似的。只不过没用monitor指令,而是应用ACC_SYNCHRONIZED标识办法的同步
    public synchronized void lock(){        System.out.println("world");    }....  public synchronized void lock();    descriptor: ()V    flags: (0x0029) ACC_PUBLIC, ACC_SYNCHRONIZED    Code:      stack=2, locals=0, args_size=0         0: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;         3: ldc           #26                 // String world         5: invokevirtual #28                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  • synchronized是可重入,非偏心锁,因为entryList的线程会先自旋尝试加锁,而不是退出cxq排队期待,不偏心

3 Object的wait和notify办法原理

  • wait,notify必须是持有以后对象锁Monitor的线程能力调用 (对象锁代指ObjectMonitor/Monitor,锁对象代指Object)
  • 下面有说到,当在sychronized中锁对象Object调用wait时会退出waitSet队列,WaitSet的元素对象就是ObjectWaiter
class ObjectWaiter : public StackObj { public:  enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;  enum Sorted  { PREPEND, APPEND, SORTED } ;  ObjectWaiter * volatile _next;  ObjectWaiter * volatile _prev;  Thread*       _thread;  ParkEvent *   _event;  volatile int  _notified ;  volatile TStates TState ;  Sorted        _Sorted ;           // List placement disposition  bool          _active ;           // Contention monitoring is enabled public:  ObjectWaiter(Thread* thread);  void wait_reenter_begin(ObjectMonitor *mon);  void wait_reenter_end(ObjectMonitor *mon);};

调用对象锁的wait()办法时,线程会被封装成ObjectWaiter,最初应用park办法挂起

//objectMonitor.cppvoid ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS){    ...    //线程封装成 ObjectWaiter对象    ObjectWaiter node(Self);    node.TState = ObjectWaiter::TS_WAIT ;    ...    //一系列判断操作,当线程的确退出WaitSet时,则应用park办法挂起    if (node._notified == 0) {        if (millis <= 0) {            Self->_ParkEvent->park () ;        } else {            ret = Self->_ParkEvent->park (millis) ;        }    }

而当对象锁应用notify()时

  • 如果waitSet为空,则间接返回
  • waitSet不为空从waitSet获取一个ObjectWaiter,而后依据不同的Policy退出到EntryList或通过Atomic::cmpxchg_ptr指令自旋操作退出cxq队列或者间接unpark唤醒
void ObjectMonitor::notify(TRAPS){    CHECK_OWNER();    //waitSet为空,则间接返回    if (_WaitSet == NULL) {        TEVENT (Empty-Notify) ;        return ;    }    ...    //通过DequeueWaiter获取_WaitSet列表中的第一个ObjectWaiter    Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ;    ObjectWaiter * iterator = DequeueWaiter() ;    if (iterator != NULL) {    ....    if (Policy == 2) {      // prepend to cxq         // prepend to cxq         if (List == NULL) {             iterator->_next = iterator->_prev = NULL ;             _EntryList = iterator ;         } else {            iterator->TState = ObjectWaiter::TS_CXQ ;            for (;;) {                ObjectWaiter * Front = _cxq ;                iterator->_next = Front ;                if (Atomic::cmpxchg_ptr (iterator, &_cxq, Front) == Front) {                    break ;                }            }         }     }
  • Object的notifyAll办法则对应voidObjectMonitor::notifyAll(TRAPS),流程和notify相似。不过会通过for循环取出WaitSet的ObjectWaiter节点,再顺次唤醒所有线程

4 jvm对synchronized的优化

  • 先介绍下32位JVM下JAVA对象头的构造

  • 偏差锁

    • 未加锁的时候,锁标记为01,蕴含哈希值、年龄分代和偏差锁标记位(0)
    • 施加偏差锁时,哈希值和一部分无用内存会转化为锁客人的线程信息,以及加锁时的工夫戳epoch,此时锁标记位没变,偏差锁标记改为1
    • 加锁时先判断以后线程id是否与MarkWord的线程id是否统一,统一则执行同步代码;不统一则查看偏差标记是否偏差,未偏差则应用CAS加锁;未偏差CAS加锁失败存在偏差锁会导致偏差锁收缩为轻量级锁,或者从新偏差
    • 偏差锁只有遇到其余线程竞争偏差锁时,持有偏差锁的线程才会开释锁,线程不会被动去开释偏差锁
  • 轻量级锁

    • 当产生多个线程竞争时,偏差锁会变为轻量级锁,锁标记位为00
    • 取得锁的线程会先将偏差锁撤销(在平安点),并在栈桢中创立锁记录LockRecord,对象的MarkWord被复制到刚创立的LockRecord,而后CAS尝试将记录LockRecord的owner指向锁对象,再将锁对象的MarkWord指向锁,加锁胜利
    • 如果CAS加锁失败,线程会自旋肯定次数加锁,再失败则降级为重量级锁

  • 重量级锁

    • 重量级锁就是下面介绍到synchronized应用监视器Monitor实现的锁机制
    • 竞争线程强烈,锁则持续收缩,变为重量级锁,也是互斥锁,锁标记位为10,MarkWord其余内容被替换为一个指向对象锁Monitor的指针
  • 自旋锁

    • 缩小不必要的CPU上下文切换;在轻量级锁降级为重量级锁时,就应用了自旋加锁的形式
  • 锁粗化

    • 屡次加锁操作在JVM外部也是种耗费,如果多个加锁能够合并为一个锁,就可缩小不必要的开销
Test.class//编译器会思考将两次加锁合并public void test(){    synchronized(this){        System.out.println("hello");       }    synchronized(this){        System.out.println("world");       }}
  • 锁打消

    • 删除不必要的加锁操作,如果变量是独属一个线程的栈变量,加不加锁都是平安的,编译器会尝试打消锁
    • 开启锁打消须要在JVM参数上设置-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
//StringBuffer的append操作会加上synchronized,//然而变量buf不加锁也平安的,编译器会把锁打消public void test() {    StringBuffer buf = new StringBuffer();    buf.append("hello").append("world");}
  • 其余锁优化办法

    • 分段锁,分段锁也并非一种理论的锁,而是一种思维;ConcurrentHashMap是学习分段锁的最好实际。次要是将大对象拆成小对象,而后对大对象的加锁操作变成对小对象加锁,减少了并行度

5 CAS的底层原理

  • volatile int i = 0; i++中,volatile类型的读写是原子同步的,然而i++却不能保障同步性,咱们该怎么呢?
  • 能够应用synchronized加锁;还有就是用CAS(比拟并替换),应用乐观锁的思维同步,先判断共享变量是否扭转,没有则更新。上面看看不同步版本的CAS
int expectedValue = 1;public boolean compareAndSet(int newValue) {    if(expectedValue == 1){        expectedValue = newValue;        return ture;    }    return false;}

在jdk是有提供同步版的CAS解决方案,其中应用了UnSafe.java的底层办法

//UnSafe.java    @HotSpotIntrinsicCandidate    public final native boolean compareAndSetInt(Object o, long offset, int expected, int x) ..    @HotSpotIntrinsicCandidate    public final native int compareAndExchangeInt(Object o, long offset, int expected, int x)...

咱们再来看看本地办法,Unsafe.cpp中的compareAndSwapInt

//unsafe.cppUNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))  UnsafeWrapper("Unsafe_CompareAndSwapInt");  oop p = JNIHandles::resolve(obj);  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;UNSAFE_END

在Linux的x86,Atomic::cmpxchg办法的实现如下

/**    1 __asm__示意汇编的开始;    2 volatile示意禁止编译器优化;//禁止指令重排    3 LOCK_IF_MP是个内联函数,      依据以后零碎是否为多核处理器,      决定是否为cmpxchg指令增加lock前缀 //内存屏障*/inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {  int mp = os::is_MP();  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"                    : "=a" (exchange_value)                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)                    : "cc", "memory");  return exchange_value;}

到这一步,能够总结到:jdk提供的CAS机制,在汇编层级,会禁止变量两侧的指令优化,而后应用cmpxchg指令比拟并更新变量值(原子性),如果是多核则应用lock锁定(缓存锁、MESI)

6 CAS同步操作的问题

  • ABA问题

    • 线程X筹备将变量的值从A改为B,然而这期间线程Y将变量的值从A改为C,而后再改为A;最初线程X检测变量值是A,并置换为B。但实际上,A曾经不再是原来的A了
    • 解决办法,是把变量定为惟一类型。值能够加上版本号,或者工夫戳。如加上版本号,线程Y的批改变为A1->B2->A3,此时线程X再更新则能够判断出A1不等于A3
  • 只能保障一个共享变量的原子操作

    • 只保障一个共享变量的原子操作,对多个共享变量同步时,循环CAS是无奈保障操作的原子

7 基于volatile + CAS 实现同步锁的原理

  • CAS只能同步一个变量的批改,咱们又应该如何用它来锁住代码块呢?
  • 先说说实现锁的因素

    • 1 同步代码块同一时刻只能有一个线程能执行
    • 2 加锁操作要happens-before同步代码块里的操作,而代码块里的操作要happens-before解锁操作
    • 3 同步代码块完结后绝对其余线程其批改的变量是可见的 (内存可见性)
  • 因素1:能够利用CAS的原子性来实现,任意时刻只有一个线程能胜利操作变量

    • 先构想CAS操作的共享变量是一个关联代码块的同步状态变量,同步开始之前先CAS更新状态变量为加锁状态,同步完结之后,再CAS状态变量为无锁状态
    • 如果期间有第二个线程来加锁,则会发现状态变量为加锁状态,则放弃执行同步代码块
  • 因素2:应用volatile润饰状态变量,禁止指令重排

    • volatile保障同步代码里的操作happens-before解锁操作,而加锁操作happens-before代码块里的操作
  • 因素3:还是用volatile,volatile变量写指令前后会插入内存屏障

    • volatile润饰的状态变量被CAS为无锁状态前,同步代码块的脏数据就会被更新,被各个线程可见
//伪代码volatile state = 0 ;   // 0-无锁 1-加锁;volatile禁止指令重排,退出内存屏障...if(cas(state, 0 , 1)){ // 1 加锁胜利,只有一个线程能胜利加锁    ...                // 2 同步代码块    cas(state, 1, 0);  // 3 解锁时2的操作具备可见性}

8 LockSupport理解一下

  • LockSupport是基于Unsafe类,由JDK提供的线程操作工具类,次要作用就是挂起线程,唤醒线程。Unsafe.park,unpark操作时,会调用以后线程的变量parker代理执行。Parker代码
JavaThread* thread=JavaThread::thread_from_jni_environment(env);...thread->parker()->park(isAbsolute != 0, time);
class PlatformParker : public CHeapObj {  protected:    //互斥变量类型    pthread_mutex_t _mutex [1] ;    //条件变量类型    pthread_cond_t  _cond  [1] ;    ...}class Parker : public os::PlatformParker {  private:    volatile int _counter ;    ...  public:    void park(bool isAbsolute, jlong time);    void unpark();    ...  }
  • 在Linux零碎下,用的POSIX线程库pthread中的mutex(互斥量),condition来实现线程的挂起、唤醒
  • 留神点:当park时,counter变量被设置为0,当unpark时,这个变量被设置为1
  • unpark和park执行程序不同时,counter和cond的状态变动如下

    • 先park后unpark; park:counter值不变,但会设置一个cond; unpark:counter先加1,查看cond存在,counter减为0
    • 先unpark后park;park:counter变为1,但不设置cond;unpark:counter减为0(线程不会因为park挂起)
    • 先屡次unpark;counter也只设置为为1

9 LockSupport.park和Object.wait区别

  • 两种形式都有具备挂起的线程的能力
  • 线程在Object.wait之后必须等到Object.notify能力唤醒
  • LockSupport能够先unpark线程,等线程执行LockSupport.park是不会挂起的,能够继续执行
  • 须要留神的是就算线程屡次unpark;也只能让线程第一次park是不会挂起

10 AbstractQueuedSynchronizer(AQS)

  • AQS其实就是基于volatile+cas实现的锁模板;如果须要线程阻塞期待,唤醒机制,则应用LockSupport挂起、唤醒线程
//AbstractQueuedSynchronizer.javapublic class AbstractQueuedSynchronizer{    //线程节点    static final class Node {        ...        volatile Node prev;        volatile Node next;        volatile Thread thread;        ...    }        ....    //head 期待队列头尾节点    private transient volatile Node head;    private transient volatile Node tail;    // The synchronization state. 同步状态    private volatile int state;      ...    //提供CAS操作,状态具体的批改由子类实现    protected final boolean compareAndSetState(int expect, int update) {        return STATE.compareAndSet(this, expect, update);    }}
  • AQS外部保护一个同步队列,元素就是包装了线程的Node
  • 同步队列中首节点是获取到锁的节点,它在开释锁的时会唤醒后继节点,后继节点获取到锁的时候,会把本人设为首节点

public final void acquire(int arg) {        if (!tryAcquire(arg) &&            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))            selfInterrupt();}
  • 线程会先尝试获取锁,失败则封装成Node,CAS退出同步队列的尾部。在退出同步队列的尾部时,会判断前驱节点是否是head结点,并尝试加锁(可能前驱节点刚好开释锁),否则线程进入阻塞期待

在AQS还存一个ConditionObject的外部类,它的应用机制和Object.wait、notify相似

//AbstractQueuedSynchronizer.javapublic class ConditionObject implements Condition, java.io.Serializable {    //条件队列;Node 复用了AQS中定义的Node    private transient Node firstWaiter;    private transient Node lastWaiter;    ...
  • 每个Condition对象外部蕴含一个Node元素的FIFO条件队列
  • 当一个线程调用Condition.await()办法,那么该线程将会开释锁、结构Node退出条件队列并进入期待状态
//相似Object.waitpublic final void await() throws InterruptedException{    ...    Node node = addConditionWaiter(); //结构Node,退出条件队列    int savedState = fullyRelease(node);    int interruptMode = 0;    while (!isOnSyncQueue(node)) {        //挂起线程        LockSupport.park(this);        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)            break;    }    //notify唤醒线程后,退出同步队列持续竞争锁    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)        interruptMode = REINTERRUPT;

  • 调用Condition.signal时,获取条件队列的首节点,将其挪动到同步队列并且利用LockSupport唤醒节点中的线程。随后继续执行wait挂起前的状态,调用acquireQueued(node, savedState)竞争同步状态
    //相似Object.notify    private void doSignal(Node first) {        do {            if ( (firstWaiter = first.nextWaiter) == null)                lastWaiter = null;            first.nextWaiter = null;        } while (!transferForSignal(first) &&                 (first = firstWaiter) != null);    }

  • volatile+cas机制保障了代码的同步性和可见性,而AQS封装了线程阻塞期待挂起,解锁唤醒其余线程的逻辑。AQS子类只需依据状态变量,判断是否可获取锁,是否开释锁胜利即可
  • 继承AQS须要选性重写以下几个接口
protected boolean tryAcquire(int arg);//尝试独占性加锁protected boolean tryRelease(int arg);//对应tryAcquire开释锁protected int tryAcquireShared(int arg);//尝试共享性加锁protected boolean tryReleaseShared(int arg);//对应tryAcquireShared开释锁protected boolean isHeldExclusively();//该线程是否正在独占资源,只有用到condition才须要取实现它

11 ReentrantLock的原理

  • ReentrantLock实现了Lock接口,并应用外部类Sync(Sync继承AbstractQueuedSynchronizer)来实现同步操作
  • ReentrantLock外部类Sync
abstract static class Sync extends AbstractQueuedSynchronizer{    ....     final boolean nonfairTryAcquire(int acquires) {            final Thread current = Thread.currentThread();            int c = getState();            if (c == 0) {                //间接CAS状态加锁,非偏心操作                if (compareAndSetState(0, acquires)) {                     setExclusiveOwnerThread(current);                    return true;                }            }    ...    //重写了tryRelease    protected final boolean tryRelease(int releases) {        c = state - releases; //扭转同步状态        ...        //批改volatile 润饰的状态变量        setState(c);         return free;    }}
  • Sync的子类NonfairSync和FairSync都重写了tryAcquire办法
  • 其中NonfairSync的tryAcquire调用父类的nonfairTryAcquire办法, FairSync则本人重写tryAcquire的逻辑。其中调用hasQueuedPredecessors()判断是否有排队Node,存在则返回false(false会导致以后线程排队期待锁)
    static final class NonfairSync extends Sync {        protected final boolean tryAcquire(int acquires) {            return nonfairTryAcquire(acquires);        }    }    ....    static final class FairSync extends Sync {        protected final boolean tryAcquire(int acquires) {            final Thread current = Thread.currentThread();            int c = getState();            if (c == 0) {                if (!hasQueuedPredecessors() &&                       compareAndSetState(0, acquires)) {                    setExclusiveOwnerThread(current);                    return true;                }            }    ....    

12 AQS排他锁的实例demo

public class TwinsLock implements Lock {    private final Sync sync = new Sync(2);    @Override    public void lockInterruptibly() throws InterruptedException {  throw new RuntimeException(""); }    @Override    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {throw new RuntimeException("");}    @Override    public Condition newCondition() {  return sync.newCondition(); }    @Override    public void lock() {  sync.acquireShared(1); }    @Override    public void unlock() {  sync.releaseShared(1); } }    @Override    public boolean tryLock() { return sync.tryAcquireShared(1) > -1;  }}

再来看看Sync的代码

class Sync extends AbstractQueuedSynchronizer {        Sync(int count) {            if (count <= 0) {                throw new IllegalArgumentException("count must large than zero");            }            setState(count);        }        @Override        public int tryAcquireShared(int reduceCount) {            for (; ; ) {                int current = getState();                 int newCount = current - reduceCount;                if (newCount < 0 || compareAndSetState(current, newCount)) {                    return newCount;                }            }        }        @Override        public boolean tryReleaseShared(int returnCount) {            for (; ; ) {                int current = getState();                int newCount = current + returnCount;                if (compareAndSetState(current, newCount)) {                    return true;                }            }        }        public Condition newCondition() {            return new AbstractQueuedSynchronizer.ConditionObject();        }    }

13 应用锁,能避免线程死循环吗

  • 答案是不肯定的;对于单个资源来说是能够做的;然而多个资源会存在死锁的状况,例如线程A持有资源X,期待资源Y,而线程B持有资源Y,期待资源X
  • 有了锁,能够对资源加状态管制,然而咱们还须要避免死锁的产生,突破产生死锁的四个条件之一就行
  • 1 资源不可反复被两个及以上的使用者占用
  • 2 使用者持有资源并期待其余资源
  • 3 资源不可被抢占
  • 4 多个使用者造成期待对方资源的循环圈

14 ThreadLocal是否可保障资源的同步

  • 当应用ThreadLocal申明变量时,ThreadLocal为每个应用该变量的线程提供独立的变量正本,每一个线程都能够独立地扭转本人的正本,而不会影响其它线程所对应的正本
  • 从下面的概念可知,ThreadLocal其实并不能保障变量的同步性,只是给每一个线程调配一个变量正本

关注公众号,大家一起交换

参考文章

  • objectMonitor.cpp
  • Moniter的实现原理
  • JVM源码剖析之Object.wait/notify实现
  • Java对象头与锁
  • LockSupport中park与unpark根本应用与原理
  • Java并发之Condition
  • 从jvm源码看synchronized