摘要:而問題往往就是有多個線程同時在執行步驟。另一個線程有機會執行轉賬操作,為當前賬戶打錢。相反的,它處于阻塞狀態,直到另一個線程調用同一條件的。喚醒所有處于該條件中的等待線程,這些線程將重新競爭鎖。
【條件競爭
在多線程的開發中,兩個及其以上的線程需要共享統一數據的存取。如果兩個線程存取相同的對象,并且每一個線程都調用一個修改該對象狀態的方法,根據線程訪問數據的順序,可能會出現錯誤的數據結果,這種現象成為條件競爭。因為修改對象狀態的方法并不是一個原子操作,通常步驟是:
1. 讀取當前狀態值到線程工作內存。 2. 在線程工作內存中修改狀態值。 3. 將修改后的狀態值重新寫入主內存。
而問題往往就是有多個線程同時在執行步驟2。
【有兩種機制代碼受并發訪問的干擾synchronized關鍵字。
Reentrantlock類。
【Reentrantlock類可重入的互斥鎖,又被稱為“獨占鎖”。Lock和synchronized機制的主要區別:
1. synchronized機制提供了對與每個對象相關的隱式監視器鎖的訪問, 并強制所有鎖獲取和釋放均要出現在一個塊結構中, 當獲取了多個鎖時,它們必須以相反的順序釋放。 2. synchronized機制對鎖的釋放是隱式的, 只要線程運行的代碼超出了synchronized語句塊范圍, 鎖就會被釋放;而Lock機制必須顯式的調用Lock對象的unlock()方法才能釋放鎖, 這為獲取鎖和釋放鎖不出現在同一個塊結構中, 以及以更自由的順序釋放鎖提供了可能。
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){}放開的話,可以看見只有一個線程能進入該方法中,說明鎖有效。
【讀寫鎖不過有一個問題出現了,如果兩個線程有寫的操作,那么上鎖是沒有問題的。 但是如果都是讀的操作那么還用不用上鎖呢?應該不用了,因為鎖是很消耗資源的,能不用就不用?;谶@樣的考慮,jdk提供了讀寫鎖:
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private Lock writeLock = readWriteLock.writeLock(); private Lock readLock = readWriteLock.readLock();
總結一些讀寫鎖最重要的特點:
多個線程包含至少一個寫操作:鎖生效。
多個線程全部是讀操作:鎖不生效。
這樣一來,我們就把讀寫分離開來,在系統查詢是大部分操作,如果將讀寫分離出來,性能就會大大受益。 有人會問:讀操作本來就不需要加鎖啊,只在寫操作上用鎖就可以了。如果是這樣的話,試想:共享對象正在執行寫操作,這時一個讀操作執行了, 但是讀操作讀出來的數據與執行完寫操作的數據不一致,這就會影響使用讀操作返回值的邏輯,這種現象就是:不可重復讀! 是的,數據庫中就使用了讀寫鎖的思想,并且劃分了事務的隔離級別。
public class LockObject2 { private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private Lock writeLock = readWriteLock.writeLock(); private Lock readLock = readWriteLock.readLock(); //共享數據 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():獲得鎖,如果鎖同時被另一個線程持有則發生阻塞。
void unlock():釋放鎖,必須在finally{}中。
【構造方法ReentrantLock():構建一個可以用來保護臨界區的可重入鎖對象。
ReentrantLock(boolean fair):構建一個帶有公平策略的鎖。公平鎖偏愛等待時間最長的線程,但是會大大降低性能,所以默認情況下,鎖不公平。 貌似公平鎖更加的合理,但是公平鎖會比常規鎖慢很多,只有當你確定自己要做什么并且對于你要解決的問題有一個特定的理由時,才使用公平鎖。并且就算是公平鎖也無法絕對的公平因為這和線程的調度器有關。
【條件對象些時候,線程進入臨界區之后,發現需要滿足一定的條件才可以執行。要使用一個條件對象來管理那些已經獲得鎖但是不能工作的線程。我們使用銀行轉賬的例子:
private ReentrantLock bankLock = new ReentrantLock(); public void lockTrans(int to ,int amount){ bankLock.lock(); try{ while (this.amount < amount){ //wait } }finally { bankLock.unlock(); } }
現在,賬戶沒有足夠金額的時候,只有等待另一個線程向賬戶中轉賬。但是這個線程已經獲得了鎖,其他線程無法執行轉賬操作啊。這就是為什么我們要使用條件對象的原因。 一個對象鎖,可以有多個相關的條件對象。
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(); } }
現在當前線程被阻塞了,并放棄了鎖。另一個線程有機會執行轉賬操作,為當前賬戶打錢。等待獲得鎖的線程與調用了await()的線程有本質區別,一旦一個線程調用了await(),它就進入該條件的等待集,當鎖可以使用時,它不能馬上解鎖。相反的,它處于阻塞狀態,直到另一個線程調用同一條件的signalAll()。
signalAll():喚醒所有處于該條件中的等待線程,這些線程將重新競爭鎖。
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(); //處理轉賬邏輯 sufficientFunds.signalAll(); } }finally { bankLock.unlock(); } }【完整的轉賬代碼如下
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(); } //轉賬邏輯 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; } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68092.html
摘要:介紹中無鎖的線程安全整數,一個提供原子操作的的類。在語言中,和操作并不是線程安全的,在使用的時候,不可避免的會用到關鍵字。而則通過一種線程安全的加減操作接口。就是的意思,比較并操作。有個操作數,內存值,舊的預期值,要修改的新值。 【介紹 JAVA 中無鎖的線程安全整數 AtomicInteger,一個提供原子操作的Integer的類。在Java語言中,++i和i++操作并不是線程安全的...
摘要:公平鎖為了保證時間上的絕對順序,需要頻繁的上下文切換,而非公平鎖會降低一定的上下文切換,降低性能開銷。因此,默認選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統更大的吞吐量。ReentrantLock簡介ReentrantLock重入鎖,是實現Lock接口的一個類,也是在實際編程中使用頻率很高的一個鎖, 支持重入性,表示能夠對共享資源能夠重復加鎖,即當前線程獲取該鎖再次獲取不會被阻...
摘要:公平鎖為了保證時間上的絕對順序,需要頻繁的上下文切換,而非公平鎖會降低一定的上下文切換,降低性能開銷。因此,默認選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統更大的吞吐量。ReentrantLock簡介ReentrantLock重入鎖,是實現Lock接口的一個類,也是在實際編程中使用頻率很高的一個鎖, 支持重入性,表示能夠對共享資源能夠重復加鎖,即當前線程獲取該鎖再次獲取不會被阻...
摘要:公平鎖為了保證時間上的絕對順序,需要頻繁的上下文切換,而非公平鎖會降低一定的上下文切換,降低性能開銷。因此,默認選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統更大的吞吐量。ReentrantLock簡介ReentrantLock重入鎖,是實現Lock接口的一個類,也是在實際編程中使用頻率很高的一個鎖, 支持重入性,表示能夠對共享資源能夠重復加鎖,即當前線程獲取該鎖再次獲取不會被阻...
摘要:作者畢來生微信鎖狀態轉換分類以后幫助我們提供了線程同步機制,通過顯示定義同步鎖來實現對象之間的同步。等待重新嘗試因為在中是用關鍵字聲明的,故可以在線程間可見再次判斷一下能否持有鎖可能線程同步代碼執行得比較快,已經釋放了鎖,不可以就返回。 作者 : 畢來生微信: 878799579 鎖狀態轉換 showImg(https://segmentfault.com/img/remote/...
閱讀 3561·2023-04-26 02:10
閱讀 1300·2021-11-22 15:25
閱讀 1668·2021-09-22 10:02
閱讀 907·2021-09-06 15:02
閱讀 3469·2019-08-30 15:55
閱讀 600·2019-08-30 13:58
閱讀 2775·2019-08-30 12:53
閱讀 3042·2019-08-29 12:38