您好,登錄后才能下訂單哦!
synchronized主要是用于解決線程安全問題的,而線程安全問題的主要誘因有如下兩點(diǎn):
解決線程安全問題的根本方法:
所以互斥鎖是解決問題的辦法之一,互斥鎖的特性如下:
互斥性:即在同一時(shí)間只允許一個(gè)線程持有某個(gè)對(duì)象鎖,通過這種特性來實(shí)現(xiàn)多線程的協(xié)調(diào)機(jī)制,這樣在同一時(shí)間只有一個(gè)線程對(duì)需要同步的代碼塊(復(fù)合操作)進(jìn)行訪問?;コ庑砸卜Q為操作的原子性
可見性:必須確保在鎖被釋放之前,對(duì)共享變量所做的修改,對(duì)于隨后獲得該鎖的另一個(gè)線程是可見的(即在獲得鎖時(shí)應(yīng)獲得最新共享變量的值),否則另一個(gè)線程可能是在本地緩存的某個(gè)副本上繼續(xù)操作,從而引起數(shù)據(jù)不一致問題
而synchronized就可以實(shí)現(xiàn)互斥鎖的特性,不過需要注意的是synchronized鎖的不是代碼,而是對(duì)象。
根據(jù)獲取的鎖可以分為兩類:
對(duì)象鎖和類鎖的總結(jié):
實(shí)現(xiàn)synchronized需要依賴兩個(gè)基礎(chǔ)概念:
Java對(duì)象在內(nèi)存中的布局主要分為三塊區(qū)域:
synchronized使用的鎖對(duì)象是存儲(chǔ)在Java對(duì)象頭里的,對(duì)象頭結(jié)構(gòu)如下:
由于對(duì)象頭信息是與對(duì)象自身定義的數(shù)據(jù)沒有關(guān)系的額外存儲(chǔ)成本,考慮到JVM的空間效率,Mark Word被設(shè)計(jì)為非固定的數(shù)據(jù)結(jié)構(gòu)以便存儲(chǔ)更多有效的數(shù)據(jù),它會(huì)根據(jù)對(duì)象自身的狀態(tài)賦予自己的存儲(chǔ)空間:
簡(jiǎn)單介紹了對(duì)象頭,接著我們來了解一下Monitor,每個(gè)Java對(duì)象天生自帶了一把看不見的鎖,它叫做內(nèi)部鎖或Monitor鎖。Monitor的主要實(shí)現(xiàn)代碼在ObjectMonitor.hpp中:
Monitor鎖的競(jìng)爭(zhēng)、獲取與釋放:
然后我們從字節(jié)碼層面上看一下synchronized,將如下代碼通過javac編譯成class文件:
package com.example.demo.thread;
/**
* @author 01
* @date 2019-07-20
**/
public class SyncBlockAndMethod {
public void syncsTask() {
synchronized (this) {
System.out.println("Hello syncsTask");
}
}
public synchronized void syncTask() {
System.out.println("Hello syncTask");
}
}
然后通過 javap -verbose 將class文件反編譯成可閱讀的字節(jié)碼內(nèi)容,如下:
Classfile /E:/Java_IDEA/demo/src/main/java/com/example/demo/thread/SyncBlockAndMethod.class
Last modified 2019年7月20日; size 637 bytes
MD5 checksum 7600723349daa088a5353acd84c80fa5
Compiled from "SyncBlockAndMethod.java"
public class com.example.demo.thread.SyncBlockAndMethod
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #6 // com/example/demo/thread/SyncBlockAndMethod
super_class: #7 // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 1
Constant pool:
#1 = Methodref #7.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #19.#20 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #21 // Hello syncsTask
#4 = Methodref #22.#23 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = String #24 // Hello syncTask
#6 = Class #25 // com/example/demo/thread/SyncBlockAndMethod
#7 = Class #26 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 syncsTask
#13 = Utf8 StackMapTable
#14 = Class #27 // java/lang/Throwable
#15 = Utf8 syncTask
#16 = Utf8 SourceFile
#17 = Utf8 SyncBlockAndMethod.java
#18 = NameAndType #8:#9 // "<init>":()V
#19 = Class #28 // java/lang/System
#20 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#21 = Utf8 Hello syncsTask
#22 = Class #31 // java/io/PrintStream
#23 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#24 = Utf8 Hello syncTask
#25 = Utf8 com/example/demo/thread/SyncBlockAndMethod
#26 = Utf8 java/lang/Object
#27 = Utf8 java/lang/Throwable
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (Ljava/lang/String;)V
{
public com.example.demo.thread.SyncBlockAndMethod();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
public void syncsTask();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter // 指向同步代碼塊的開始位置
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String Hello syncsTask
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit // 指向同步代碼塊的結(jié)束位置,monitorenter和monitorexit之間就是同步代碼塊
14: goto 22
17: astore_2
18: aload_1
19: monitorexit // 若代碼發(fā)生異常時(shí)就會(huì)執(zhí)行這句指令釋放鎖
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
LineNumberTable:
line 10: 0
line 11: 4
line 12: 12
line 13: 22
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 17
locals = [ class com/example/demo/thread/SyncBlockAndMethod, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
public synchronized void syncTask();
descriptor: ()V
flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED // 用于標(biāo)識(shí)是一個(gè)同步方法,不需要像同步塊那樣需要通過顯式的字節(jié)碼指令去標(biāo)識(shí)哪里需要獲取鎖,哪里需要釋放鎖。同步方法無論是正常執(zhí)行還是發(fā)生異常都會(huì)釋放鎖
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String Hello syncTask
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 16: 0
line 17: 8
}
SourceFile: "SyncBlockAndMethod.java"
什么是重入:
從互斥鎖的設(shè)計(jì)上來說,當(dāng)一個(gè)線程試圖操作一個(gè)由其他線程持有的對(duì)象鎖的臨界資源時(shí),將會(huì)處于阻塞狀態(tài),但當(dāng)一個(gè)線程再次請(qǐng)求自己持有對(duì)象鎖的臨界資源時(shí),這種情況屬于重入
為什么會(huì)對(duì)synchronized嗤之以鼻:
鎖優(yōu)化之自旋鎖:
許多情況下,共享數(shù)據(jù)的鎖定狀態(tài)持續(xù)時(shí)間較短,切換線程不值得。于是自旋鎖應(yīng)運(yùn)而生,所謂自旋就是通過讓線程執(zhí)行忙循環(huán)等待鎖的釋放,從而不讓出CPU時(shí)間片,例如while某個(gè)標(biāo)識(shí)變量
缺點(diǎn):若鎖被其他線程長(zhǎng)時(shí)間占用,將會(huì)帶來許多性能上的開銷,所以一般超過指定的自旋次數(shù)就會(huì)將線程掛起處于阻塞狀態(tài)
鎖優(yōu)化之自適應(yīng)自旋鎖:
自適應(yīng)自旋鎖與普通自旋鎖不同的就是可以自適應(yīng)自旋次數(shù),即自旋次數(shù)不再固定。而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來決定
鎖優(yōu)化之鎖消除,鎖消除是JVM另一種鎖優(yōu)化,這種優(yōu)化更徹底:
在JIT編譯時(shí),對(duì)運(yùn)行上下文進(jìn)行掃描,去除不可能存在資源競(jìng)爭(zhēng)的鎖。這種方式可以消除不必要的鎖,可以減少毫無意義的請(qǐng)求鎖時(shí)間
關(guān)于鎖消除,我們可以看一個(gè)例子,代碼如下:
public class StringBufferWithoutSync {
public void add(String str1, String str2) {
//StringBuffer是線程安全,由于sb只會(huì)在append方法中使用,不可能被其他線程引用
//因此sb屬于不可能共享的資源,JVM會(huì)自動(dòng)消除內(nèi)部的鎖
StringBuffer sb = new StringBuffer();
sb.append(str1).append(str2);
}
public static void main(String[] args) {
StringBufferWithoutSync withoutSync = new StringBufferWithoutSync();
for (int i = 0; i < 1000; i++) {
withoutSync.add("aaa", "bbb");
}
}
}
鎖優(yōu)化之鎖粗化,我們?cè)賮砹私怄i粗化的概念,有些情況下可能會(huì)需要頻繁且重復(fù)進(jìn)行加鎖和解鎖操作,例如同步代碼寫在循環(huán)語句里,此時(shí)JVM會(huì)有鎖粗化的機(jī)制,即通過擴(kuò)大加鎖的范圍,以避免反復(fù)加鎖和解鎖操作。代碼示例:
public class CoarseSync {
public static String copyString100Times(String target){
int i = 0;
// JVM會(huì)將鎖粗化到外部,使得重復(fù)的加解鎖操作只需要進(jìn)行一次
StringBuffer sb = new StringBuffer();
while (i < 100){
sb.append(target);
}
return sb.toString();
}
}
synchronized鎖存在四種狀態(tài):
偏向鎖:
大多數(shù)情況下,鎖不存在多線程競(jìng)爭(zhēng),總是由同一線程多次獲得,為了減少同一線程獲取鎖的代價(jià),就會(huì)使用偏向鎖
核心思想:
如果一個(gè)線程獲得了鎖,那么鎖就進(jìn)入偏向模式,此時(shí)Mark Word的結(jié)構(gòu)也變?yōu)槠蜴i結(jié)構(gòu),當(dāng)該線程再次請(qǐng)求鎖時(shí),無需再做任何同步操作,即獲取鎖的過程只需要檢查Mark Word的鎖標(biāo)記位為偏向鎖以及當(dāng)前線程id等于Mark Word的ThreadID即可,這樣就省去了大量有關(guān)鎖申請(qǐng)的操作,那么這個(gè)鎖也就偏向于該線程了偏向鎖不適用于鎖競(jìng)爭(zhēng)比較激烈的多線程場(chǎng)合
輕量級(jí)鎖:
輕量級(jí)鎖是由偏向鎖升級(jí)而來,偏向鎖運(yùn)行在一個(gè)線程進(jìn)入同步塊的情況下,當(dāng)有第二個(gè)線程加入鎖競(jìng)爭(zhēng)時(shí),偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖
適用場(chǎng)景:線程交替執(zhí)行同步塊
若存在線程同一時(shí)間訪問同一鎖的情況,就會(huì)導(dǎo)致輕量級(jí)鎖膨脹為重量級(jí)鎖
輕量級(jí)鎖的加鎖過程:
在代碼進(jìn)入同步塊的時(shí)候,如果同步對(duì)象鎖狀態(tài)為無鎖狀態(tài)(鎖標(biāo)志位為“01”狀態(tài)),虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(LockRecord)的空間,用于存儲(chǔ)鎖對(duì)象目前的Mark Word的拷貝,官方稱之為Displaced Mark Word。這時(shí)候線程堆棧與對(duì)象頭的狀態(tài)如下圖所示:
如果這個(gè)更新動(dòng)作成功了,那么這個(gè)線程就擁有了該對(duì)象的鎖,并且對(duì)象Mark Word的鎖標(biāo)志位設(shè)置為“00",即表示此對(duì)象處于輕量級(jí)鎖定狀態(tài),這時(shí)候線程堆棧與對(duì)象頭的狀態(tài)如下圖所示:
輕量級(jí)鎖的解鎖過程:
鎖的內(nèi)存語義:
當(dāng)線程釋放鎖時(shí),Java內(nèi)存模型會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存中;而當(dāng)線程獲取鎖時(shí),Java內(nèi)存模型會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無效,從而使得被監(jiān)視器保護(hù)的臨界區(qū)代碼必須從主內(nèi)存中讀取共享變量
偏向鎖、輕量級(jí)鎖、重量級(jí)鎖的匯總:
在JDK1.5之前,synchronized是Java唯一的同步手段,而在1.5之后則有了ReentrantLock類(重入鎖):
ReentrantLock公平性的設(shè)置:
ReentrantLock fairLock = new ReentrantLock(true);
ReentrantLock的好處在于將鎖對(duì)象化了,因此可以實(shí)現(xiàn)synchronized難以實(shí)現(xiàn)的邏輯,例如:
如果說ReentrantLock將synchronized轉(zhuǎn)變?yōu)榱丝煽氐膶?duì)象,那么是否能將wait、notify及notifyall等方法對(duì)象化,答案是有的,即Condition:
synchronized和ReentrantLock的區(qū)別:
Java內(nèi)存模型(JMM):
Java內(nèi)存模型(Java Memory Model,簡(jiǎn)稱JMM)本身是一種抽象的概念,并不真實(shí)存在,它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個(gè)變量(包括實(shí)例字段,靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素)的訪問方式
JMM中的主內(nèi)存(即堆空間):
JMM中的工作內(nèi)存(即本地內(nèi)存,或線程棧):
JMM與Java內(nèi)存區(qū)域劃分(即Java內(nèi)存結(jié)構(gòu))是不同的概念層次:
主內(nèi)存與工作內(nèi)存的數(shù)據(jù)存儲(chǔ)類型以及操作方式歸納:
JMM如何解決可見性問題:
指令重排序需要滿足的條件:
什么是Java內(nèi)存模型中的happens-before:
happens-before的八大原則:
- 程序次序規(guī)則:一個(gè)線程內(nèi),按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作
- 鎖定規(guī)則:一個(gè)unLock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作
- volatile變量規(guī)則:對(duì)一個(gè)變量的寫操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作(保證了可見性)
- 傳遞規(guī)則:如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C
- 線程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作
- 線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生
- 線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測(cè),我們可以通過Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測(cè)到線程已經(jīng)終止執(zhí)行
- 對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于他的finalize()方法的開始
volatile:
volatile變量為何立即可見?簡(jiǎn)單來說:
volatile變量如何禁止重排序優(yōu)化:
volatile和synchronized的區(qū)別:
- volatile本質(zhì)是在告訴JVM當(dāng)前變量在寄存器(工作內(nèi)存)中的值是不確定的,需要從主存中讀?。籹ynchronized則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問該變量,其他線程被阻塞住直到該線程完成變量操作為止
- volatile僅能使用在變量級(jí)別;synchronized則可以使用在變量、方法和類級(jí)別
- volatile僅能實(shí)現(xiàn)變量的修改可見性,不能保證原子性;而synchronized則可以保證變量修改的可見性和原子性
- volatile不會(huì)造成線程的阻塞;synchronized可能會(huì)造成線程的阻塞
- volatile標(biāo)記的變量不會(huì)被編譯器優(yōu)化;synchronized標(biāo)記的變量可以被編譯器優(yōu)化
CAS(Compare and Swap)是一種線程安全性的方法:
CAS思想:
CAS多數(shù)情況下對(duì)開發(fā)者來說是透明的:
缺點(diǎn):
利用Executors創(chuàng)建不同的線程池滿足不同場(chǎng)景的需求:
- newFixedThreadPool(int nThreads):指定工作線程數(shù)量的線程池
- newCachedThreadPool():處理大量短時(shí)間工作任務(wù)的線程池,特點(diǎn):
- 試圖緩存線程并重用,當(dāng)無緩存線程可用時(shí),就會(huì)創(chuàng)建新的工作線程
- 如果線程閑置的時(shí)間超過閾值,則會(huì)被終止并移出緩存
- 系統(tǒng)長(zhǎng)時(shí)間閑置的時(shí)候,不會(huì)消耗什么資源
- newSingleThreadExecutor():創(chuàng)建唯一的工作者線程來執(zhí)行任務(wù),如果線程異常結(jié)束,會(huì)有另一個(gè)線程取代它
- newSingleThreadScheduledExecutor()與newScheduledThreadPool(int corePoolSize):定時(shí)或者周期性的工作調(diào)度,兩者的區(qū)別在于單一工作線程還是多個(gè)線程
- JDK8新增的newWorkStealingPool():內(nèi)部會(huì)構(gòu)建ForkJoinPool ,利用working-stealing算法,并行地處理任務(wù),不保證處理順序
- working-stealing算法:某個(gè)線程從其他線程的任務(wù)隊(duì)列里竊取任務(wù)來執(zhí)行
Fork/Join框架(JDK7提供):
為什么要使用線程池:
Executor的框架:
J.U.C的三個(gè)Executor接口:
線程池執(zhí)行任務(wù)流程圖:
ThreadPoolExecutor的七個(gè)構(gòu)造器參數(shù):
int corePoolSize
:核心線程數(shù)int maximumPoolSize
:最大線程數(shù)long keepAliveTime
:線程空閑存活時(shí)間TimeUnit unit
:存活時(shí)間的單位BlockingQueue<Runnable> workQueue
:任務(wù)等待隊(duì)列ThreadFactory threadFactory
:線程創(chuàng)建工廠,用于創(chuàng)建新線程RejectedExecutionHandler handler
:任務(wù)拒絕策略
新任務(wù)提交execute執(zhí)行后的判斷:
execute執(zhí)行流程圖:
線程池的狀態(tài):
線程池狀態(tài)轉(zhuǎn)換圖:
線程池中工作線程的生命周期:
關(guān)于線程池大小如何選定參考:
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。