java 独占锁_锁分类(独占锁、分拆锁、分离锁、分布式锁)
一、java內(nèi)存模型
提到同步、鎖,就必須提到Java的內(nèi)存模型,為了提高程序的執(zhí)行效率,java也吸收了傳統(tǒng)應(yīng)用程序的多級(jí)緩存體系。
在共享內(nèi)存的多處理器體系架構(gòu)中,每個(gè)處理器都擁有自己的緩存,并且定期地與主內(nèi)存進(jìn)行協(xié)調(diào)。在不同的處理器架構(gòu)中提供了不同級(jí)別的緩存一致性(Cache Coherence),其中一部分只提供最小的保證,即允許不同的處理器在任意時(shí)刻從同一個(gè)存儲(chǔ)位置上看到不同的值。操作系統(tǒng)、編譯器以及運(yùn)行時(shí)(有時(shí)甚至包括應(yīng)用程序)需要彌合這種在硬件能力與線程安全之間的差異。
要想確保每個(gè)處理器都能在任意時(shí)刻知道其他處理器正在進(jìn)行的工作,將需要非常大的開銷。在大多數(shù)時(shí)間里,這種信息是不必要的。因此處理器會(huì)適當(dāng)放寬存儲(chǔ)一致性保證,以換取性能的提升。在架構(gòu)定義的內(nèi)存模型中將告訴應(yīng)用程序可以從內(nèi)存系統(tǒng)中獲得怎樣的保證,此外還定義了一些特殊的指令(稱為內(nèi)存柵欄),當(dāng)需要共享數(shù)據(jù)時(shí),這些指令就能實(shí)現(xiàn)額外的存儲(chǔ)協(xié)調(diào)保證。為了使java開發(fā)人員無須關(guān)心不同架構(gòu)內(nèi)存模型之間的差異,Java還提供了自己的內(nèi)存模型,并且JVM通過在適當(dāng)?shù)奈恢蒙喜迦雰?nèi)存柵欄來屏蔽在JVM與底層之平臺(tái)內(nèi)存模型之間的差異。
經(jīng)過上面的講解和上圖,我們知道線程在運(yùn)行時(shí)候有一塊內(nèi)存專用區(qū)域,Java程序會(huì)將變量同步到線程所在的內(nèi)存。這時(shí)候會(huì)操作工作內(nèi)存中的變量,而線程中的變量何時(shí)同步回到內(nèi)存是不可預(yù)期的。但是java內(nèi)存模型規(guī)定,通過關(guān)鍵詞”synchronized“、”volatile“可以讓java保證某些約束。
“volatile” - 保證讀寫的都是主內(nèi)存變量。
“synchronized” - 保證在塊開始時(shí),都同步主內(nèi)存值到工作內(nèi)存,而快結(jié)束時(shí),將工作內(nèi)存同步會(huì)主內(nèi)存。
重排序
public classPossibleReordering {static int x = 0,y=0;static int a=0,b=0;public static void main(String[] args) throwsInterruptedException {
Thread one= new Thread(newRunnable() {
@Overridepublic voidrun() {
a= 1;
x=b;
}
});
Thread two= new Thread(newRunnable() {
@Overridepublic voidrun() {
b= 2;
y=a;
}
});
one.start();two.start();
one.join();two.join();
System.out.println("x:" + x+",y:"+y);
}
}
重排序。如上圖,執(zhí)行結(jié)果,一般人可能認(rèn)為是1,1;真正的執(zhí)行結(jié)果可能每次都不一樣。拜JMM重排序所賜,JMM使得不同線程的操作順序是不同的,從而導(dǎo)致在缺乏同步的情況下,要推斷操作的執(zhí)行結(jié)果將變得更加復(fù)雜。各種使操作延遲或看似亂序執(zhí)行的不同原因,都可以歸為重排序。內(nèi)存級(jí)的重排序會(huì)使程序的行為變得不可預(yù)測(cè)。如果沒有同步,要推斷出程序的執(zhí)行順序是非常困難的,而要確保在程序中正確的使用同步卻是非常容易的。同步將限制編譯器和硬件運(yùn)行時(shí)對(duì)內(nèi)存操作重排序的方式。
鎖synchronized
鎖實(shí)現(xiàn)了對(duì)臨界資源的互斥訪問,被synchronized修飾的代碼只有一條線程可以通過,是嚴(yán)格的排它鎖、互斥鎖。沒有獲得對(duì)應(yīng)鎖對(duì)象監(jiān)視器(monitor)的線程會(huì)進(jìn)入等待隊(duì)列,任何線程必須獲得monitor的所有權(quán)才可以進(jìn)入同步塊,退出同步快或者遇到異常都要釋放所有權(quán),JVM規(guī)范通過兩個(gè)內(nèi)存屏障(memory barrier)命令來實(shí)現(xiàn)排它邏輯。內(nèi)存屏障可以理解成順序執(zhí)行的一組CPU指令,完全無視指令重排序。
什么是鎖
public class TestStatic {
public syncronized static void write(boolean flag) {
xxxxx
}
public synchronized static void read() {
xxxxx
}
}
線程1訪問TestStatic.write()方法時(shí),線程2能訪問TestStatic.read()方法嗎
線程1訪問new TestStatic().write()方法時(shí),線程2能訪問new TestStatic().read()方法嗎
線程1訪問TestStatic.write()方法時(shí),線程2能訪問new TestStatic().read()方法嗎
public class Test {
public syncronized void write(boolean flag)?{
xxxxx
}
public synchronized void read() {
xxxxx
}
}
Test test = new Test();線程1訪問test.write() 方法,線程2能否訪問test.read()方法
Test a = new Test(); Test b = new Test();線程1訪問a.write()訪問,線程2能否訪問b.read()方法
答案,java中每個(gè)對(duì)象都可以作為一個(gè)鎖,而對(duì)象就決定了鎖的粒度大小。
對(duì)于實(shí)例同步方法,鎖是當(dāng)前對(duì)象。
對(duì)于靜態(tài)方法,鎖是TestSTatic.class對(duì)象
對(duì)于同步代碼塊,鎖是Synchronized括號(hào)里面配置的對(duì)象
TestStatic類,1問,作用范圍全體class對(duì)象,線程1拿到,線程2就不能拿到
2問,3問同上
Test類,1問,不能,鎖都是實(shí)例對(duì)象test,線程1拿到鎖之后,線程2無法訪問
2問,可以,線程1鎖是實(shí)例a,線程2是實(shí)例b。
獨(dú)占鎖
如果你不敢確定該用什么鎖,就用這個(gè)吧,在保證正確的前提下,后續(xù)在提高開發(fā)效率。
public class ServerStatus {
public final Set users;
public final Set quers;
public synchronized void addUser(String u ) {
users.add(u);
}
public synchronized void addQuery(String q ) {
quers.add(q);
}
public synchronized void removeUser(String u) {
users.remove(u);
}
public synchronized void removeQuery(String q) {
quers.remove(q);
}
}
分拆鎖
如果在整個(gè)應(yīng)用程序只有一個(gè)鎖,而不是為每個(gè)對(duì)象分配一個(gè)獨(dú)立的鎖,那么所有同步代碼塊的執(zhí)行就會(huì)變成串行化執(zhí)行。由于很多線程都會(huì)競(jìng)爭同一個(gè)全局鎖,因此兩個(gè)線程同時(shí)請(qǐng)求這個(gè)鎖的概率將會(huì)劇增,從而導(dǎo)致更嚴(yán)重的競(jìng)爭。所以如果將這些鎖請(qǐng)求分到更多的鎖上,就能有效降低鎖競(jìng)爭程度。由于等待而被阻塞的線程將更少,從而可伸縮性將提高。
上文中users、quers是兩個(gè)相互獨(dú)立的變量,可以將此分解為兩個(gè)獨(dú)立的鎖,每個(gè)鎖只保護(hù)一個(gè)變量,降低每個(gè)鎖被請(qǐng)求的頻率。
public class ServerStatus {
public final Set users;
public final Set quers;
public void addUser(String u ) {
synchronized(users) {
users.add(u);
}
}
public void addQuery(String q ) {
synchronized(quers) {
quers.add(q);
}
}
public void removeUser(String u) {
synchronized(users) {
users.remove(u);
}
}
public void removeQuery(String q) {
synchronized(quers) {
quers.remove(q);
}
}
}
分離鎖
在某些情況下,可以將鎖分解技術(shù)進(jìn)一步擴(kuò)展為對(duì)一組獨(dú)立對(duì)象上的鎖進(jìn)行分解,這種情況稱為鎖分段。例如ConcurrencyHashMap是有一個(gè)包含16個(gè)鎖的數(shù)組實(shí)現(xiàn),每個(gè)鎖保護(hù)所有散列桶的1/16,其中第N個(gè)散列桶由第(N mod 16)個(gè)鎖來保護(hù)。假設(shè)所有關(guān)鍵字都時(shí)間均與分布,那么相當(dāng)于把鎖的請(qǐng)求減少到原來的1/16,可以支持多達(dá)16個(gè)的并發(fā)寫入。
鎖分段的劣勢(shì)在于:與采用單個(gè)鎖來實(shí)現(xiàn)獨(dú)占訪問相比,要獲取多個(gè)鎖來實(shí)現(xiàn)獨(dú)占訪問將更加困難并且開銷更高,比如計(jì)算size、重hash。
分布式鎖
zookeeper,判斷臨時(shí)節(jié)點(diǎn)是否存在,存在就說明已經(jīng)有人爭搶到鎖;不存在就創(chuàng)建節(jié)點(diǎn),表明擁有該鎖。
記下,以后詳細(xì)研究
《分布式鎖實(shí)現(xiàn):數(shù)據(jù)庫、redis、zookeeper》
volatile
volatile是比synchronized更輕量級(jí)的同步原語,volatile可以修飾實(shí)例變量、靜態(tài)變量、以及數(shù)組變量(網(wǎng)上大牛說,維護(hù)的是引用,但是里面的對(duì)象。。。嘿嘿嘿)。被volatile修飾的變量,JVM規(guī)范規(guī)定,一個(gè)線程在修改完,另外的線程能讀取最新的值。
但僅僅保證可見性,不保證原子性,所以volatile通常用來修飾boolean類型或者狀態(tài)比較少的數(shù)據(jù)類型,而且不能用來更新依賴變量之前值的操作(例volatile++)。
volatile內(nèi)部僅僅是對(duì)變量的操作多了一條cpu指令(lock#指令),它會(huì)強(qiáng)制寫數(shù)據(jù)到緩存,如果緩存數(shù)據(jù)同時(shí)也在主存,會(huì)強(qiáng)制寫數(shù)據(jù)更新到主存,并且使所有持有該主存數(shù)據(jù)地址的緩存統(tǒng)統(tǒng)失效,觸發(fā)其他持有緩存數(shù)據(jù)的線程從主存獲取最新數(shù)據(jù),從而實(shí)現(xiàn)同步。
總結(jié)
以上是生活随笔為你收集整理的java 独占锁_锁分类(独占锁、分拆锁、分离锁、分布式锁)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python做用友财务报表_Spread
- 下一篇: xshell报编码问题时可以修改xshe