摘要:而問題往往就是有多個(gè)線程同時(shí)在執(zhí)行步驟。另一個(gè)線程有機(jī)會(huì)執(zhí)行轉(zhuǎn)賬操作,為當(dāng)前賬戶打錢。相反的,它處于阻塞狀態(tài),直到另一個(gè)線程調(diào)用同一條件的。喚醒所有處于該條件中的等待線程,這些線程將重新競(jìng)爭(zhēng)鎖。
【條件競(jìng)爭(zhēng)
在多線程的開發(fā)中,兩個(gè)及其以上的線程需要共享統(tǒng)一數(shù)據(jù)的存取。如果兩個(gè)線程存取相同的對(duì)象,并且每一個(gè)線程都調(diào)用一個(gè)修改該對(duì)象狀態(tài)的方法,根據(jù)線程訪問數(shù)據(jù)的順序,可能會(huì)出現(xiàn)錯(cuò)誤的數(shù)據(jù)結(jié)果,這種現(xiàn)象成為條件競(jìng)爭(zhēng)。因?yàn)樾薷膶?duì)象狀態(tài)的方法并不是一個(gè)原子操作,通常步驟是:
1. 讀取當(dāng)前狀態(tài)值到線程工作內(nèi)存。 2. 在線程工作內(nèi)存中修改狀態(tài)值。 3. 將修改后的狀態(tài)值重新寫入主內(nèi)存。
而問題往往就是有多個(gè)線程同時(shí)在執(zhí)行步驟2。
【有兩種機(jī)制代碼受并發(fā)訪問的干擾synchronized關(guān)鍵字。
Reentrantlock類。
【Reentrantlock類可重入的互斥鎖,又被稱為“獨(dú)占鎖”。Lock和synchronized機(jī)制的主要區(qū)別:
1. synchronized機(jī)制提供了對(duì)與每個(gè)對(duì)象相關(guān)的隱式監(jiān)視器鎖的訪問, 并強(qiáng)制所有鎖獲取和釋放均要出現(xiàn)在一個(gè)塊結(jié)構(gòu)中, 當(dāng)獲取了多個(gè)鎖時(shí),它們必須以相反的順序釋放。 2. synchronized機(jī)制對(duì)鎖的釋放是隱式的, 只要線程運(yùn)行的代碼超出了synchronized語句塊范圍, 鎖就會(huì)被釋放;而Lock機(jī)制必須顯式的調(diào)用Lock對(duì)象的unlock()方法才能釋放鎖, 這為獲取鎖和釋放鎖不出現(xiàn)在同一個(gè)塊結(jié)構(gòu)中, 以及以更自由的順序釋放鎖提供了可能。
public class LockObject { private ReentrantLock lock = new ReentrantLock(); public void lockSet(int value){ lock.lock(); try{ System.out.println("lock writing : " + value); // while (1==1){} }finally { lock.unlock(); } } public void unLock(){ System.out.println("unlock"); } }
如果我們把while(1==1){}放開的話,可以看見只有一個(gè)線程能進(jìn)入該方法中,說明鎖有效。
【讀寫鎖不過有一個(gè)問題出現(xiàn)了,如果兩個(gè)線程有寫的操作,那么上鎖是沒有問題的。 但是如果都是讀的操作那么還用不用上鎖呢?應(yīng)該不用了,因?yàn)殒i是很消耗資源的,能不用就不用。基于這樣的考慮,jdk提供了讀寫鎖:
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private Lock writeLock = readWriteLock.writeLock(); private Lock readLock = readWriteLock.readLock();
總結(jié)一些讀寫鎖最重要的特點(diǎn):
多個(gè)線程包含至少一個(gè)寫操作:鎖生效。
多個(gè)線程全部是讀操作:鎖不生效。
這樣一來,我們就把讀寫分離開來,在系統(tǒng)查詢是大部分操作,如果將讀寫分離出來,性能就會(huì)大大受益。 有人會(huì)問:讀操作本來就不需要加鎖啊,只在寫操作上用鎖就可以了。如果是這樣的話,試想:共享對(duì)象正在執(zhí)行寫操作,這時(shí)一個(gè)讀操作執(zhí)行了, 但是讀操作讀出來的數(shù)據(jù)與執(zhí)行完寫操作的數(shù)據(jù)不一致,這就會(huì)影響使用讀操作返回值的邏輯,這種現(xiàn)象就是:不可重復(fù)讀! 是的,數(shù)據(jù)庫中就使用了讀寫鎖的思想,并且劃分了事務(wù)的隔離級(jí)別。
public class LockObject2 { private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private Lock writeLock = readWriteLock.writeLock(); private Lock readLock = readWriteLock.readLock(); //共享數(shù)據(jù) private int shareValue = 100; public void setLock(int value){ writeLock.lock(); try{ System.out.println("writing : " + value); shareValue = value; while (1==1){ } }finally { writeLock.unlock(); } } public void getLock(){ readLock.lock(); try{ System.out.println("getting : " + shareValue); while (1==1){ } }finally { readLock.unlock(); } } }【常用方法
void lock():獲得鎖,如果鎖同時(shí)被另一個(gè)線程持有則發(fā)生阻塞。
void unlock():釋放鎖,必須在finally{}中。
【構(gòu)造方法ReentrantLock():構(gòu)建一個(gè)可以用來保護(hù)臨界區(qū)的可重入鎖對(duì)象。
ReentrantLock(boolean fair):構(gòu)建一個(gè)帶有公平策略的鎖。公平鎖偏愛等待時(shí)間最長的線程,但是會(huì)大大降低性能,所以默認(rèn)情況下,鎖不公平。 貌似公平鎖更加的合理,但是公平鎖會(huì)比常規(guī)鎖慢很多,只有當(dāng)你確定自己要做什么并且對(duì)于你要解決的問題有一個(gè)特定的理由時(shí),才使用公平鎖。并且就算是公平鎖也無法絕對(duì)的公平因?yàn)檫@和線程的調(diào)度器有關(guān)。
【條件對(duì)象些時(shí)候,線程進(jìn)入臨界區(qū)之后,發(fā)現(xiàn)需要滿足一定的條件才可以執(zhí)行。要使用一個(gè)條件對(duì)象來管理那些已經(jīng)獲得鎖但是不能工作的線程。我們使用銀行轉(zhuǎn)賬的例子:
private ReentrantLock bankLock = new ReentrantLock(); public void lockTrans(int to ,int amount){ bankLock.lock(); try{ while (this.amount < amount){ //wait } }finally { bankLock.unlock(); } }
現(xiàn)在,賬戶沒有足夠金額的時(shí)候,只有等待另一個(gè)線程向賬戶中轉(zhuǎn)賬。但是這個(gè)線程已經(jīng)獲得了鎖,其他線程無法執(zhí)行轉(zhuǎn)賬操作啊。這就是為什么我們要使用條件對(duì)象的原因。 一個(gè)對(duì)象鎖,可以有多個(gè)相關(guān)的條件對(duì)象。
private ReentrantLock bankLock = new ReentrantLock(); private Condition sufficientFunds = bankLock.newCondition(); public void lockTrans(int to ,int amount) throws InterruptedException { bankLock.lock(); try{ while (this.amount < amount){ //wait sufficientFunds.await(); } }finally { bankLock.unlock(); } }
現(xiàn)在當(dāng)前線程被阻塞了,并放棄了鎖。另一個(gè)線程有機(jī)會(huì)執(zhí)行轉(zhuǎn)賬操作,為當(dāng)前賬戶打錢。等待獲得鎖的線程與調(diào)用了await()的線程有本質(zhì)區(qū)別,一旦一個(gè)線程調(diào)用了await(),它就進(jìn)入該條件的等待集,當(dāng)鎖可以使用時(shí),它不能馬上解鎖。相反的,它處于阻塞狀態(tài),直到另一個(gè)線程調(diào)用同一條件的signalAll()。
signalAll():?jiǎn)拘阉刑幱谠摋l件中的等待線程,這些線程將重新競(jìng)爭(zhēng)鎖。
private ReentrantLock bankLock = new ReentrantLock(); private Condition sufficientFunds = bankLock.newCondition(); public void lockTrans(int to ,int amount) throws InterruptedException { bankLock.lock(); try{ while (this.amount < amount){ //wait sufficientFunds.await(); //處理轉(zhuǎn)賬邏輯 sufficientFunds.signalAll(); } }finally { bankLock.unlock(); } }【完整的轉(zhuǎn)賬代碼如下
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.CompletableFuture; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Bank { public static void main(String[] args) { Bank b = new Bank(5,100); Random random = new Random(); Listcfs = new ArrayList<>(); for (int i = 0; i < b.size(); i++) { int from = i; int to = (int) (b.size() * Math.random()); int max = 100; int min = 10; int amount = random.nextInt(max) % (max - min + 1) + min; cfs.add(CompletableFuture.runAsync(() -> { try { b.transfer(from, to, amount); } catch (InterruptedException e) { e.printStackTrace(); } }) ); } CompletableFuture.allOf(cfs.toArray(new CompletableFuture[cfs.size()])).join(); } private final int[] accounts; private Lock bankLock; private Condition sufficientFunds; public Bank(int n,int initialBalance){ accounts = new int[n]; Arrays.fill(accounts,initialBalance); bankLock = new ReentrantLock(); sufficientFunds = bankLock.newCondition(); } //轉(zhuǎn)賬邏輯 public void transfer(int from,int to,int amount) throws InterruptedException { bankLock.lock(); try { while (accounts[from] < amount){ sufficientFunds.await(); } System.out.println(Thread.currentThread()); accounts[from] -= amount; System.out.println("---------------------------------------"); System.out.println(String.format("%d from %d to %d",amount,from,to)); accounts[to] += amount; System.out.println(String.format("Total balance:%d",getTotalBalance())); System.out.println("Detail:"); for (int i = 0; i < accounts.length; i++) { System.out.println(String.format("account[%d]"s amount is : %d",i,accounts[i])); } System.out.println("---------------------------------------"); sufficientFunds.signalAll(); }finally { bankLock.unlock(); } } public int getTotalBalance(){ bankLock.lock(); try{ int sum = 0; for(int a : accounts){ sum += a; } return sum; }finally { bankLock.unlock(); } } public int size(){ return accounts.length; } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/68092.html
摘要:介紹中無鎖的線程安全整數(shù),一個(gè)提供原子操作的的類。在語言中,和操作并不是線程安全的,在使用的時(shí)候,不可避免的會(huì)用到關(guān)鍵字。而則通過一種線程安全的加減操作接口。就是的意思,比較并操作。有個(gè)操作數(shù),內(nèi)存值,舊的預(yù)期值,要修改的新值。 【介紹 JAVA 中無鎖的線程安全整數(shù) AtomicInteger,一個(gè)提供原子操作的Integer的類。在Java語言中,++i和i++操作并不是線程安全的...
摘要:公平鎖為了保證時(shí)間上的絕對(duì)順序,需要頻繁的上下文切換,而非公平鎖會(huì)降低一定的上下文切換,降低性能開銷。因此,默認(rèn)選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統(tǒng)更大的吞吐量。ReentrantLock簡(jiǎn)介ReentrantLock重入鎖,是實(shí)現(xiàn)Lock接口的一個(gè)類,也是在實(shí)際編程中使用頻率很高的一個(gè)鎖, 支持重入性,表示能夠?qū)蚕碣Y源能夠重復(fù)加鎖,即當(dāng)前線程獲取該鎖再次獲取不會(huì)被阻...
摘要:公平鎖為了保證時(shí)間上的絕對(duì)順序,需要頻繁的上下文切換,而非公平鎖會(huì)降低一定的上下文切換,降低性能開銷。因此,默認(rèn)選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統(tǒng)更大的吞吐量。ReentrantLock簡(jiǎn)介ReentrantLock重入鎖,是實(shí)現(xiàn)Lock接口的一個(gè)類,也是在實(shí)際編程中使用頻率很高的一個(gè)鎖, 支持重入性,表示能夠?qū)蚕碣Y源能夠重復(fù)加鎖,即當(dāng)前線程獲取該鎖再次獲取不會(huì)被阻...
摘要:公平鎖為了保證時(shí)間上的絕對(duì)順序,需要頻繁的上下文切換,而非公平鎖會(huì)降低一定的上下文切換,降低性能開銷。因此,默認(rèn)選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統(tǒng)更大的吞吐量。ReentrantLock簡(jiǎn)介ReentrantLock重入鎖,是實(shí)現(xiàn)Lock接口的一個(gè)類,也是在實(shí)際編程中使用頻率很高的一個(gè)鎖, 支持重入性,表示能夠?qū)蚕碣Y源能夠重復(fù)加鎖,即當(dāng)前線程獲取該鎖再次獲取不會(huì)被阻...
摘要:作者畢來生微信鎖狀態(tài)轉(zhuǎn)換分類以后幫助我們提供了線程同步機(jī)制,通過顯示定義同步鎖來實(shí)現(xiàn)對(duì)象之間的同步。等待重新嘗試因?yàn)樵谥惺怯藐P(guān)鍵字聲明的,故可以在線程間可見再次判斷一下能否持有鎖可能線程同步代碼執(zhí)行得比較快,已經(jīng)釋放了鎖,不可以就返回。 作者 : 畢來生微信: 878799579 鎖狀態(tài)轉(zhuǎn)換 showImg(https://segmentfault.com/img/remote/...
閱讀 3611·2023-04-26 02:10
閱讀 1397·2021-11-22 15:25
閱讀 1702·2021-09-22 10:02
閱讀 945·2021-09-06 15:02
閱讀 3505·2019-08-30 15:55
閱讀 635·2019-08-30 13:58
閱讀 2807·2019-08-30 12:53
閱讀 3092·2019-08-29 12:38