Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
Java并发问题–乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
</h1><div class="clear"></div><div class="postBody">
首先介绍一些乐观锁与悲观锁:
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
乐观锁的一种实现方式-CAS(Compare and Swap 比较并交换):
锁存在的问题:
Java在JDK1.5之前都是靠 synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。这就是一种独占锁,独占锁其实就是一种悲观锁,所以可以说 synchronized 是悲观锁。
悲观锁机制存在以下问题:
1. 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
2. 一个线程持有锁会导致其它所有需要此锁的线程挂起。
3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
对比于悲观锁的这些问题,另一个更加有效的锁就是乐观锁。其实乐观锁就是:每次不加锁而是假设没有并发冲突而去完成某项操作,如果因为并发冲突失败就重试,直到成功为止。
乐观锁:
乐观锁( Optimistic Locking )在上文已经说过了,其实就是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。
上面提到的乐观锁的概念中其实已经阐述了它的具体实现细节:主要就是两个步骤:冲突检测和数据更新。其实现方式有一种比较典型的就是 Compare and Swap ( CAS )。
CAS:
CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“ 我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。 ”这其实和乐观锁的冲突检查+数据更新的原理是一样的。
这里再强调一下,乐观锁是一种思想。CAS是这种思想的一种实现方式。
JAVA对CAS的支持:
在JDK1.5 中新增 java.util.concurrent (J.U.C)就是建立在CAS之上的。相对于对于 synchronized 这种阻塞算法,CAS是非阻塞算法的一种常见实现。所以J.U.C在性能上有了很大的提升。
以 java.util.concurrent 中的 AtomicInteger 为例,看一下在不使用锁的情况下是如何保证线程安全的。主要理解 getAndIncrement 方法,该方法的作用相当于 ++i 操作。
1 public class AtomicInteger extends Number implements java.io.Serializable { 2 private volatile int value; 3 4 public final int get() { 5 return value; 6 } 7 8 public final int getAndIncrement() { 9 for (;;) { 10 int current = get(); 11 int next = current + 1; 12 if (compareAndSet(current, next)) 13 return current; 14 } 15 } 16 17 public final boolean compareAndSet(int expect, int update) { 18 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 19 } 20 }
在没有锁的机制下,字段value要借助volatile原语,保证线程间的数据是可见性。这样在获取变量的值的时候才能直接读取。然后来看看 ++i 是怎么做到的。
getAndIncrement 采用了CAS操作,每次从内存中读取数据然后将此数据和 +1 后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。
而 compareAndSet 利用JNI(Java Native Interface)来完成CPU指令的操作:
1 public final boolean compareAndSet(int expect, int update) { 2 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 3 }
其中unsafe.compareAndSwapInt(this, valueOffset, expect, update);类似如下逻辑:
1 if (this == expect) { 2 this = update 3 return true; 4 } else { 5 return false; 6 }
那么比较this == expect,替换this = update,compareAndSwapInt实现这两个步骤的原子性呢? 参考CAS的原理
CAS原理:
CAS通过调用JNI的代码实现的。而compareAndSwapInt就是借助C来调用CPU底层指令实现的。
下面从分析比较常用的CPU(intel x86)来解释CAS的实现原理。
下面是sun.misc.Unsafe类的compareAndSwapInt()方法的源代码:
1 public final native boolean compareAndSwapInt(Object o, long offset, 2 int expected, 3 int x);
可以看到这是个本地方法调用。这个本地方法在JDK中依次调用的C++代码为:
1 #define LOCK_IF_MP(mp) __asm cmp mp, 0 \2 __asm je L0 \3 __asm _emit 0xF0 \4 __asm L0:5 6 inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {7 // alternative for InterlockedCompareExchange8 int mp = os::is_MP();9 __asm { 10 mov edx, dest 11 mov ecx, exchange_value 12 mov eax, compare_value 13 LOCK_IF_MP(mp) 14 cmpxchg dword ptr [edx], ecx 15 } 16 }
如上面源代码所示,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。
CAS缺点:
1. ABA问题:
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但可能存在潜藏的问题。如下所示:
现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:
head.compareAndSet(A,B);
在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A,此时堆栈结构如下图,而对象B此时处于游离状态:
此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,所以此时的情况变为:
其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
1 public boolean compareAndSet( 2 V expectedReference,//预期引用 3 4 V newReference,//更新后的引用 5 6 int expectedStamp, //预期标志 7 8 int newStamp //更新后的标志 9 )
实际应用代码:
1 private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100, 0); 2 3 ........ 4 5 atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
2. 循环时间长开销大:
自旋CAS(不成功,就一直循环执行,直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
3. 只能保证一个共享变量的原子操作:
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
CAS与Synchronized的使用情景:
1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。
concurrent包的实现:
由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:
1. A线程写volatile变量,随后B线程读这个volatile变量。
2. A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
3. A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
4. A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。
Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
1. 首先,声明共享变量为volatile;
2. 然后,使用CAS的原子条件更新来实现线程之间的同步;
3. 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:
JVM中的CAS(堆中对象的分配):
Java调用new object()
会创建一个对象,这个对象会被分配到JVM的堆中。那么这个对象到底是怎么在堆中保存的呢?
首先,new object()
执行的时候,这个对象需要多大的空间,其实是已经确定的,因为java中的各种数据类型,占用多大的空间都是固定的(对其原理不清楚的请自行Google)。那么接下来的工作就是在堆中找出那么一块空间用于存放这个对象。
在单线程的情况下,一般有两种分配策略:
1. 指针碰撞:这种一般适用于内存是绝对规整的(内存是否规整取决于内存回收策略),分配空间的工作只是将指针像空闲内存一侧移动对象大小的距离即可。
2. 空闲列表:这种适用于内存非规整的情况,这种情况下JVM会维护一个内存列表,记录哪些内存区域是空闲的,大小是多少。给对象分配空间的时候去空闲列表里查询到合适的区域然后进行分配即可。
但是JVM不可能一直在单线程状态下运行,那样效率太差了。由于再给一个对象分配内存的时候不是原子性的操作,至少需要以下几步:查找空闲列表、分配内存、修改空闲列表等等,这是不安全的。解决并发时的安全问题也有两种策略:
1. CAS:实际上虚拟机采用CAS配合上失败重试的方式保证更新操作的原子性,原理和上面讲的一样。
2. TLAB:如果使用CAS其实对性能还是会有影响的,所以JVM又提出了一种更高级的优化策略:每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲区(TLAB),线程内部需要分配内存时直接在TLAB上分配就行,避免了线程冲突。只有当缓冲区的内存用光需要重新分配内存的时候才会进行CAS操作分配更大的内存空间。
虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB
参数来进行配置(jdk5及以后的版本默认是启用TLAB的)。
<div id="blog_post_info">
<div class="clear"></div>
<div id="post_next_prev"><a href="https://www.cnblogs.com/qjjazry/p/6544811.html" class="p_n_p_prefix">« </a> 上一篇: <a href="https://www.cnblogs.com/qjjazry/p/6544811.html" title="发布于 2017-03-13 20:16">计算机网络(谢希仁版)--运输层</a>
<br>
<a href="https://www.cnblogs.com/qjjazry/p/6581697.html" class="p_n_p_prefix">» </a> 下一篇: <a href="https://www.cnblogs.com/qjjazry/p/6581697.html" title="发布于 2017-03-19 19:46">Java并发编程--并发容器之Collections</a>
</div><!--end: topics 文章、评论容器-->
</div>
#1楼
2018-04-05 21:02
<a id="a_comment_author_3943118" href="https://www.cnblogs.com/water-xu/" target="_blank">成长与蜕变</a></div><div class="feedbackCon">
https://pic.cnblogs.com/face/813458/20150919134036.png
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#2楼
2018-05-02 20:43
<a id="a_comment_author_3965690" href="https://home.cnblogs.com/u/953189/" target="_blank">家伟码家庭</a></div><div class="feedbackCon">
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#3楼
2018-05-27 20:52
<a id="a_comment_author_3983352" href="https://www.cnblogs.com/scuwangjun/" target="_blank">scuwangjun</a></div><div class="feedbackCon">
https://pic.cnblogs.com/face/1354771/20180318150644.png
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#4楼
2018-07-05 16:43
<a id="a_comment_author_4013415" href="https://home.cnblogs.com/u/1148512/" target="_blank">YouCii</a></div><div class="feedbackCon">
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#5楼
2018-08-22 09:42
<a id="a_comment_author_4047789" href="https://www.cnblogs.com/takemybreathaway/" target="_blank">我和我的倔强</a></div><div class="feedbackCon">
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#6楼
2018-09-06 16:18
<a id="a_comment_author_4059950" href="https://home.cnblogs.com/u/1474255/" target="_blank">东风吹</a></div><div class="feedbackCon">
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#7楼
2018-09-25 14:28
<a id="a_comment_author_4075537" href="https://www.cnblogs.com/aishangJava/" target="_blank">工程师搁浅</a></div><div class="feedbackCon">
https://pic.cnblogs.com/face/990913/20170317155723.png
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#8楼
2018-10-22 09:55
<a id="a_comment_author_4095501" href="https://www.cnblogs.com/super-chao/" target="_blank">super超人</a></div><div class="feedbackCon">
https://pic.cnblogs.com/face/1003101/20160810150622.png
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#9楼
2018-11-19 14:51
<a id="a_comment_author_4117620" href="https://home.cnblogs.com/u/1418474/" target="_blank">白堂主</a></div><div class="feedbackCon">
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#10楼
2019-03-05 21:39
<a id="a_comment_author_4194523" href="https://home.cnblogs.com/u/1619345/" target="_blank">本篇吧测试人</a></div><div class="feedbackCon">
软件测试学科在国内被越来越重视,但遗憾的是网上测试习的资料比较少,也比较零散,不成体系。故开发测试技术分享网站:奔跑吧!测试人(runtester。com),主要是想和大家分享测试相关的知识,通过原创、转载一些高质量的测试技术文章,不断沉淀优质内容,希望对大家有所帮助。
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#11楼
2019-03-20 18:08
<a id="a_comment_author_4208485" href="https://www.cnblogs.com/longsanshi/" target="_blank">longtengdama</a></div><div class="feedbackCon">
https://pic.cnblogs.com/face/725581/20171031145226.png
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#12楼
2019-03-21 16:49
<a id="a_comment_author_4209515" href="https://home.cnblogs.com/u/1548712/" target="_blank">小熙看世界ZZX</a></div><div class="feedbackCon">
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#13楼
2019-07-21 08:31
<a id="a_comment_author_4305085" href="https://www.cnblogs.com/sunny-boy/" target="_blank">生活在阳光下</a></div><div class="feedbackCon">
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#14楼
2019-10-21 12:12
<a id="a_comment_author_4398588" href="https://home.cnblogs.com/u/1752186/" target="_blank">loong1120</a></div><div class="feedbackCon">
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#15楼
2020-03-10 20:22
<a id="a_comment_author_4517527" href="https://home.cnblogs.com/u/1967239/" target="_blank">陌子蒨</a></div><div class="feedbackCon">
ATOMIC();
int old_reg_val = *reg;
if (old_reg_val == oldval)
*reg = newval;
END_ATOMIC();
这个过程不是已经说明 读取到内存中的值到写入新值都是原子性操作了吗,为什么还会有线程更改变量的值导致ABA,
我的理解是:线程a在读取到内存的值A后,时间片段到了,以至于线程b也读到了A,进行了一定修改之后,线程a才竞争到CPU可以继续执行这个时候发现值还是A,不知道是不是这么个原因?
</div></div><div class="feedbackItem"><div class="feedbackListSubtitle"><div class="feedbackManage">
</div>
#16楼
<span id="comment-maxId" style="display:none">4523362</span><span id="comment-maxDate" style="display:none">2020/3/16 下午4:51:02</span>
2020-03-16 16:51
<a id="a_comment_author_4523362" href="https://www.cnblogs.com/ljl150/" target="_blank">小L要努力吖</a></div><div class="feedbackCon">
@super超人
乐观锁和悲观锁并没有优劣之分,它们有各自适合的场景
https://pic.cnblogs.com/face/1804577/20200223202153.png
</div></div>
【活动】腾讯云服务器推出云产品采购季 1核2G首年仅需99元
【推荐】Java经典面试题整理及答案详解(一)
【推荐】开放下载!《长安十二时辰》爆款背后的优酷技术秘籍首次公开
· 乐观锁和悲观锁及CAS实现
· 乐观锁与悲观锁
· java多线程系列3:悲观锁和乐观锁
· 乐观锁的一种实现方式——CAS
· Java多线程之悲观锁与乐观锁
» 更多推荐...
<div id="google_ads_iframe_/1090369/C2_0__container__" style="border: 0pt none;"><iframe id="google_ads_iframe_/1090369/C2_0" title="3rd party ad content" name="google_ads_iframe_/1090369/C2_0" width="468" height="60" scrolling="no" marginwidth="0" marginheight="0" frameborder="0" srcdoc="" style="border: 0px; vertical-align: bottom;" data-google-container-id="2" data-load-complete="true"></iframe></div></div>
</div>
<div id="under_post_kb">
· 可以抓取物体并轻松移动的软体机器人你见过没
· 带货这一仗,罗永浩输不起
· 快手和抖音有什么区别?对比细节后有了意想不到的发现!
· 扎克伯格:和其他Facebook员工一样,我也在居家办公
· 代码泄露三星Note 20搭载骁龙865 配新版One UI2.5系统
» 更多新闻...
Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS相关推荐
- Java并发 乐观锁和悲观锁 乐观锁的一种实现方式CAS
为什么80%的码农都做不了架构师?>>> 首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人 ...
- java学习系列2(并发锁问题-乐观锁与悲观锁以及乐观锁的一种实现方式-CAS)
Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS 首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别 ...
- 悲观锁和乐观锁_乐观锁和悲观锁 以及 乐观锁的一种实现方式-CAS
悲观锁 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞知道它拿到锁.传统的关系型数据库里面就用到了很多的这种锁机制,比如行锁,表锁等 ...
- 乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...
- 乐观锁的一种实现方式——CAS
转载自 乐观锁的一种实现方式--CAS 在深入理解乐观锁与悲观锁一文中我们介绍过锁.本文在这篇文章的基础上,深入分析一下乐观锁的实现机制,介绍什么是CAS.CAS的应用以及CAS存在的问题等. 线程安 ...
- 乐观构思、悲观计划、乐观实行
乐观构思.悲观计划.乐观实行 开拓新事业并让它获得成功的人,多数是天性乐观的人,他们能够开朗明快地描绘自己的未来. "头脑里闪过这样的念头,按现在的情况实现的可能性不高,但要是拼命努力的话, ...
- java并发编程(三十五)——公平与非公平锁实战
前言 在 java并发编程(十六)--锁的七大分类及特点 一文中我们对锁有各个维度的分类,其中有一个维度是公平/非公平,本文我们来探讨下公平与非公平锁. 公平|非公平 首先,我们来看下什么是公平锁和非 ...
- java并发编程(二十六)——单例模式的双重检查锁模式为什么必须加 volatile?
前言 本文我们从一个问题出发来进行探究关于volatile的应用. 问题:单例模式的双重检查锁模式为什么必须加 volatile? 什么是单例模式 单例模式指的是,保证一个类只有一个实例,并且提供一个 ...
- java并发编程基础系列(五): 创建线程的四种方式
线程的创建一共有四种方式: 继承于Thread类,重写run()方法: 实现Runable接口,实现里面的run()方法: 使用 FutureTask 实现有返回结果的线程 使用ExecutorSer ...
最新文章
- nginx+tomcat,http强制跳转https后的error_page配置!【原创】
- Android Service 服务(二)—— BroadcastReceiver
- python字典的增,删,改,查
- NVIDIA GPU 架构梳理
- 如何从SAP Fiori Launchpad里找到ABAP Development Tool的下载地址
- 前端学习(3153):react-hello-react之脚手架文件介绍
- 简明 Python 教程学习笔记_2_函数
- esxi虚机启动慢的问题
- 20190616 IDEA-每次修改JS文件都需要重启Idea才能生效解决方法
- gem install XXX报错
- 发电机机房设计规范_柴油发电机的容量怎么选择!发电机机房如何合理设计?来涨知识!...
- php mysql 跨库_PHP使用PDO进行跨数据库操作
- Python及IPython安装使用
- android手机空间占用,安卓手机清理数据八种方法教程,解决占用手机空间的烦恼!...
- CSS3 之 童年的纸飞机
- win10删除多余账户_Win10系统如何删除账户?Win10系统删除账户的方法
- python训练营 朋友圈点赞收费吗_千万不要随便在朋友圈点赞!
- 星起航:短视频内容的流量迸发让电商渠道看到了新的趋势
- mysql怎么建只读账号报错_如何创建只读MySQL用户?
- 二目运算符多个条件判断的写法
热门文章
- MailKit和MimeKit 收发邮件
- Rmd输出pdf报错
- Tomb.Finance的每周更新(5.23-5.29)
- 广州男子花12万买新车 保养时被告知车门被撬开维修过
- 【解决方案】智慧水利:EasyNVR+EasyNVS视频监控解决方案
- 微信PC版重大更新!电脑上也能玩小游戏了
- 4月10日服务器例行维护公告,4月10日服务器例行维护公告(已完成)
- references to other resources are not supported by build-time PNG generation.
- 2.0 案例2:爬取房源信息以及分析房价
- 关于图片加载的问题-如加载失败显示占位图,预加载,懒加载