热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

关抢占自旋锁_基础篇:详解锁原理,synchronized、volatile+cas底层实现

随着多进程多线程的出现,对共享资源(设备,数据等)的竞争往往会导致资源的使用表现为随机无序例如:一个线程想在控制台输出Iamfine&
  • 随着多进程多线程的出现,对共享资源(设备,数据等)的竞争往往会导致资源的使用表现为随机无序
  • 例如:一个线程想在控制台输出"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 &#61; ObjectWaiter::TS_WAIT ; ... //一系列判断操作&#xff0c;当线程确实加入WaitSet时&#xff0c;则使用park方法挂起 if (node._notified &#61;&#61; 0) { if (millis <&#61; 0) { Self->_ParkEvent->park () ; } else { ret &#61; Self->_ParkEvent->park (millis) ; } }

而当对象锁使用notify()时

  • 如果waitSet为空&#xff0c;则直接返回
  • waitSet不为空从waitSet获取一个ObjectWaiter&#xff0c;然后根据不同的Policy加入到EntryList或通过Atomic::cmpxchg_ptr指令自旋操作加入cxq队列或者直接unpark唤醒

void ObjectMonitor::notify(TRAPS){ CHECK_OWNER(); //waitSet为空&#xff0c;则直接返回 if (_WaitSet &#61;&#61; NULL) { TEVENT (Empty-Notify) ; return ; } ... //通过DequeueWaiter获取_WaitSet列表中的第一个ObjectWaiter Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ; ObjectWaiter * iterator &#61; DequeueWaiter() ; if (iterator !&#61; NULL) { .... if (Policy &#61;&#61; 2) { // prepend to cxq // prepend to cxq if (List &#61;&#61; NULL) { iterator->_next &#61; iterator->_prev &#61; NULL ; _EntryList &#61; iterator ; } else { iterator->TState &#61; ObjectWaiter::TS_CXQ ; for (;;) { ObjectWaiter * Front &#61; _cxq ; iterator->_next &#61; Front ; if (Atomic::cmpxchg_ptr (iterator, &_cxq, Front) &#61;&#61; Front) { break ; } } } }

  • Object的notifyAll方法则对应voidObjectMonitor::notifyAll(TRAPS)&#xff0c;流程和notify类似。不过会通过for循环取出WaitSet的ObjectWaiter节点&#xff0c;再依次唤醒所有线程

4 jvm对synchronized的优化
  • 先介绍下32位JVM下JAVA对象头的结构
  • 偏向锁 未加锁的时候&#xff0c;锁标志为01&#xff0c;包含哈希值、年龄分代和偏向锁标志位(0)施加偏向锁时&#xff0c;哈希值和一部分无用内存会转化为锁主人的线程信息&#xff0c;以及加锁时的时间戳epoch&#xff0c;此时锁标志位没变&#xff0c;偏向锁标志改为1加锁时先判断当前线程id是否与MarkWord的线程id是否一致&#xff0c;一致则执行同步代码&#xff1b;不一致则检查偏向标志是否偏向&#xff0c;未偏向则使用CAS加锁&#xff1b;未偏向CAS加锁失败存在偏向锁会导致偏向锁膨胀为轻量级锁&#xff0c;或者重新偏向偏向锁只有遇到其他线程竞争偏向锁时&#xff0c;持有偏向锁的线程才会释放锁&#xff0c;线程不会主动去释放偏向锁
  • 轻量级锁 当发生多个线程竞争时&#xff0c;偏向锁会变为轻量级锁&#xff0c;锁标志位为00获得锁的线程会先将偏向锁撤销(在安全点)&#xff0c;并在栈桢中创建锁记录LockRecord&#xff0c;对象的MarkWord被复制到刚创建的LockRecord&#xff0c;然后CAS尝试将记录LockRecord的owner指向锁对象&#xff0c;再将锁对象的MarkWord指向锁&#xff0c;加锁成功如果CAS加锁失败&#xff0c;线程会自旋一定次数加锁&#xff0c;再失败则升级为重量级锁
  • 重量级锁 重量级锁就是上面介绍到synchronized使用监视器Monitor实现的锁机制竞争线程激烈&#xff0c;锁则继续膨胀&#xff0c;变为重量级锁&#xff0c;也是互斥锁&#xff0c;锁标志位为10&#xff0c;MarkWord其余内容被替换为一个指向对象锁Monitor的指针
  • 自旋锁 减少不必要的CPU上下文切换&#xff1b;在轻量级锁升级为重量级锁时&#xff0c;就使用了自旋加锁的方式
  • 锁粗化 多次加锁操作在JVM内部也是种消耗&#xff0c;如果多个加锁可以合并为一个锁&#xff0c;就可减少不必要的开销

Test.class//编译器会考虑将两次加锁合并public void test(){ synchronized(this){ System.out.println("hello"); } synchronized(this){ System.out.println("world"); }}

  • 锁消除 删除不必要的加锁操作&#xff0c;如果变量是独属一个线程的栈变量&#xff0c;加不加锁都是安全的&#xff0c;编译器会尝试消除锁开启锁消除需要在JVM参数上设置-server -XX:&#43;DoEscapeAnalysis -XX:&#43;EliminateLocks

//StringBuffer的append操作会加上synchronized&#xff0c;//但是变量buf不加锁也安全的&#xff0c;编译器会把锁消除public void test() { StringBuffer buf &#61; new StringBuffer(); buf.append("hello").append("world");}

  • 其他锁优化方法 分段锁&#xff0c;分段锁也并非一种实际的锁&#xff0c;而是一种思想&#xff1b;ConcurrentHashMap是学习分段锁的最好实践。主要是将大对象拆成小对象&#xff0c;然后对大对象的加锁操作变成对小对象加锁&#xff0c;增加了并行度

5 CAS的底层原理
  • 在volatile int i &#61; 0; i&#43;&#43;中&#xff0c;volatile类型的读写是原子同步的&#xff0c;但是i&#43;&#43;却不能保证同步性&#xff0c;我们该怎么呢&#xff1f;
  • 可以使用synchronized加锁&#xff1b;还有就是用CAS(比较并交换)&#xff0c;使用乐观锁的思想同步&#xff0c;先判断共享变量是否改变&#xff0c;没有则更新。下面看看不同步版本的CAS

int expectedValue &#61; 1;public boolean compareAndSet(int newValue) { if(expectedValue &#61;&#61; 1){ expectedValue &#61; newValue; return ture; } return false;}

在jdk是有提供同步版的CAS解决方案&#xff0c;其中使用了UnSafe.java的底层方法

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

我们再来看看本地方法&#xff0c;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 &#61; JNIHandles::resolve(obj); jint* addr &#61; (jint *) index_oop_from_field_offset_long(p, offset); return (jint)(Atomic::cmpxchg(x, addr, e)) &#61;&#61; e;UNSAFE_END

在Linux的x86&#xff0c;Atomic::cmpxchg方法的实现如下

/** 1 __asm__表示汇编的开始&#xff1b; 2 volatile表示禁止编译器优化&#xff1b;//禁止指令重排 3 LOCK_IF_MP是个内联函数&#xff0c; 根据当前系统是否为多核处理器&#xff0c; 决定是否为cmpxchg指令添加lock前缀 //内存屏障*/inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { int mp &#61; os::is_MP(); __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" : "&#61;a" (exchange_value) : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp) : "cc", "memory"); return exchange_value;}

到这一步&#xff0c;可以总结到&#xff1a;jdk提供的CAS机制&#xff0c;在汇编层级&#xff0c;会禁止变量两侧的指令优化&#xff0c;然后使用cmpxchg指令比较并更新变量值(原子性)&#xff0c;如果是多核则使用lock锁定(缓存锁、MESI)

6 CAS同步操作的问题
  • ABA问题 线程X准备将变量的值从A改为B&#xff0c;然而这期间线程Y将变量的值从A改为C&#xff0c;然后再改为A&#xff1b;最后线程X检测变量值是A&#xff0c;并置换为B。但实际上&#xff0c;A已经不再是原来的A了解决方法&#xff0c;是把变量定为唯一类型。值可以加上版本号&#xff0c;或者时间戳。如加上版本号&#xff0c;线程Y的修改变为A1->B2->A3&#xff0c;此时线程X再更新则可以判断出A1不等于A3
  • 只能保证一个共享变量的原子操作 只保证一个共享变量的原子操作&#xff0c;对多个共享变量同步时&#xff0c;循环CAS是无法保证操作的原子

7 基于volatile &#43; CAS 实现同步锁的原理
  • CAS只能同步一个变量的修改&#xff0c;我们又应该如何用它来锁住代码块呢&#xff1f;
  • 先说说实现锁的要素 1 同步代码块同一时刻只能有一个线程能执行2 加锁操作要happens-before同步代码块里的操作&#xff0c;而代码块里的操作要happens-before解锁操作3 同步代码块结束后相对其他线程其修改的变量是可见的 (内存可见性)
  • 要素1&#xff1a;可以利用CAS的原子性来实现&#xff0c;任意时刻只有一个线程能成功操作变量 先设想CAS操作的共享变量是一个关联代码块的同步状态变量&#xff0c;同步开始之前先CAS更新状态变量为加锁状态&#xff0c;同步结束之后&#xff0c;再CAS状态变量为无锁状态如果期间有第二个线程来加锁&#xff0c;则会发现状态变量为加锁状态&#xff0c;则放弃执行同步代码块
  • 要素2&#xff1a;使用volatile修饰状态变量&#xff0c;禁止指令重排 volatile保证同步代码里的操作happens-before解锁操作&#xff0c;而加锁操作happens-before代码块里的操作
  • 要素3&#xff1a;还是用volatile&#xff0c;volatile变量写指令前后会插入内存屏障 volatile修饰的状态变量被CAS为无锁状态前&#xff0c;同步代码块的脏数据就会被更新&#xff0c;被各个线程可见

//伪代码volatile state &#61; 0 ; // 0-无锁 1-加锁&#xff1b;volatile禁止指令重排&#xff0c;加入内存屏障...if(cas(state, 0 , 1)){ // 1 加锁成功&#xff0c;只有一个线程能成功加锁 ... // 2 同步代码块 cas(state, 1, 0); // 3 解锁时2的操作具有可见性}

8 LockSupport了解一下
  • LockSupport是基于Unsafe类&#xff0c;由JDK提供的线程操作工具类&#xff0c;主要作用就是挂起线程&#xff0c;唤醒线程。Unsafe.park&#xff0c;unpark操作时&#xff0c;会调用当前线程的变量parker代理执行。Parker代码

JavaThread* thread&#61;JavaThread::thread_from_jni_environment(env);...thread->parker()->park(isAbsolute !&#61; 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系统下&#xff0c;用的POSIX线程库pthread中的mutex(互斥量)&#xff0c;condition来实现线程的挂起、唤醒
  • 注意点&#xff1a;当park时&#xff0c;counter变量被设置为0&#xff0c;当unpark时&#xff0c;这个变量被设置为1
  • unpark和park执行顺序不同时&#xff0c;counter和cond的状态变化如下 先park后unpark; park&#xff1a;counter值不变&#xff0c;但会设置一个cond; unpark&#xff1a;counter先加1&#xff0c;检查cond存在&#xff0c;counter减为0先unpark后park&#xff1b;park&#xff1a;counter变为1&#xff0c;但不设置cond&#xff1b;unpark&#xff1a;counter减为0(线程不会因为park挂起)先多次unpark&#xff1b;counter也只设置为为1

9 LockSupport.park和Object.wait区别
  • 两种方式都有具有挂起的线程的能力
  • 线程在Object.wait之后必须等到Object.notify才能唤醒
  • LockSupport可以先unpark线程&#xff0c;等线程执行LockSupport.park是不会挂起的&#xff0c;可以继续执行
  • 需要注意的是就算线程多次unpark&#xff1b;也只能让线程第一次park是不会挂起

10 AbstractQueuedSynchronizer(AQS)
  • AQS其实就是基于volatile&#43;cas实现的锁模板&#xff1b;如果需要线程阻塞等待&#xff0c;唤醒机制&#xff0c;则使用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操作&#xff0c;状态具体的修改由子类实现 protected final boolean compareAndSetState(int expect, int update) { return STATE.compareAndSet(this, expect, update); }}

  • AQS内部维护一个同步队列&#xff0c;元素就是包装了线程的Node
  • 同步队列中首节点是获取到锁的节点&#xff0c;它在释放锁的时会唤醒后继节点&#xff0c;后继节点获取到锁的时候&#xff0c;会把自己设为首节点

public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();}

  • 线程会先尝试获取锁&#xff0c;失败则封装成Node&#xff0c;CAS加入同步队列的尾部。在加入同步队列的尾部时&#xff0c;会判断前驱节点是否是head结点&#xff0c;并尝试加锁(可能前驱节点刚好释放锁)&#xff0c;否则线程进入阻塞等待

在AQS还存一个ConditionObject的内部类&#xff0c;它的使用机制和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()方法&#xff0c;那么该线程将会释放锁、构造Node加入条件队列并进入等待状态

//类似Object.waitpublic final void await() throws InterruptedException{ ... Node node &#61; addConditionWaiter(); //构造Node,加入条件队列 int savedState &#61; fullyRelease(node); int interruptMode &#61; 0; while (!isOnSyncQueue(node)) { //挂起线程 LockSupport.park(this); if ((interruptMode &#61; checkInterruptWhileWaiting(node)) !&#61; 0) break; } //notify唤醒线程后&#xff0c;加入同步队列继续竞争锁 if (acquireQueued(node, savedState) && interruptMode !&#61; THROW_IE) interruptMode &#61; REINTERRUPT;

f108067d8295d35f8cdccf84f8de1bb9.png
  • 调用Condition.signal时&#xff0c;获取条件队列的首节点&#xff0c;将其移动到同步队列并且利用LockSupport唤醒节点中的线程。随后继续执行wait挂起前的状态&#xff0c;调用acquireQueued(node, savedState)竞争同步状态

//类似Object.notify private void doSignal(Node first) { do { if ( (firstWaiter &#61; first.nextWaiter) &#61;&#61; null) lastWaiter &#61; null; first.nextWaiter &#61; null; } while (!transferForSignal(first) && (first &#61; firstWaiter) !&#61; null); }

6457ead297af5ed2e439badba8022aeb.png
  • volatile&#43;cas机制保证了代码的同步性和可见性&#xff0c;而AQS封装了线程阻塞等待挂起&#xff0c;解锁唤醒其他线程的逻辑。AQS子类只需根据状态变量&#xff0c;判断是否可获取锁&#xff0c;是否释放锁成功即可
  • 继承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();//该线程是否正在独占资源&#xff0c;只有用到condition才需要取实现它

11 ReentrantLock的原理
4550ffa9dc02c94b8b6289e9f9a81fe7.png
  • ReentrantLock实现了Lock接口&#xff0c;并使用内部类Sync(Sync继承AbstractQueuedSynchronizer)来实现同步操作
  • ReentrantLock内部类Sync

abstract static class Sync extends AbstractQueuedSynchronizer{ .... final boolean nonfairTryAcquire(int acquires) { final Thread current &#61; Thread.currentThread(); int c &#61; getState(); if (c &#61;&#61; 0) { //直接CAS状态加锁&#xff0c;非公平操作 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } ... //重写了tryRelease protected final boolean tryRelease(int releases) { c &#61; state - releases; //改变同步状态 ... //修改volatile 修饰的状态变量 setState(c); return free; }}

  • Sync的子类NonfairSync和FairSync都重写了tryAcquire方法
  • 其中NonfairSync的tryAcquire调用父类的nonfairTryAcquire方法, FairSync则自己重写tryAcquire的逻辑。其中调用hasQueuedPredecessors()判断是否有排队Node&#xff0c;存在则返回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 &#61; Thread.currentThread(); int c &#61; getState(); if (c &#61;&#61; 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } ....

12 AQS排他锁的实例demo

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

再来看看Sync的代码

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

13 使用锁&#xff0c;能防止线程死循环吗
  • 答案是不一定的&#xff1b;对于单个资源来说是可以做的&#xff1b;但是多个资源会存在死锁的情况&#xff0c;例如线程A持有资源X&#xff0c;等待资源Y&#xff0c;而线程B持有资源Y&#xff0c;等待资源X
  • 有了锁&#xff0c;可以对资源加状态控制&#xff0c;但是我们还需要防止死锁的产生&#xff0c;打破产生死锁的四个条件之一就行
  • 1 资源不可重复被两个及以上的使用者占用
  • 2 使用者持有资源并等待其他资源
  • 3 资源不可被抢占
  • 4 多个使用者形成等待对方资源的循环圈

14 ThreadLocal是否可保证资源的同步
  • 当使用ThreadLocal声明变量时&#xff0c;ThreadLocal为每个使用该变量的线程提供独立的变量副本&#xff0c;每一个线程都可以独立地改变自己的副本&#xff0c;而不会影响其它线程所对应的副本
  • 从上面的概念可知&#xff0c;ThreadLocal其实并不能保证变量的同步性&#xff0c;只是给每一个线程分配一个变量副本


作者&#xff1a;clswcl
链接&#xff1a;https://juejin.im/post/5f13f2235188252e362e2e97
来源&#xff1a;掘金
著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。



推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • RouterOS 5.16软路由安装图解教程
    本文介绍了如何安装RouterOS 5.16软路由系统,包括系统要求、安装步骤和登录方式。同时提供了详细的图解教程,方便读者进行操作。 ... [详细]
author-avatar
手机用户2602885351
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有