Web全堆疊~35.顯式鎖
上一期
介面Lock
顯式鎖介面的定義

lock()/unlock():就是普通的獲取鎖和釋放鎖方法,lock()會阻塞直到成功,
lockInterruptibly():與lock()的不同是,它可以回應中斷,如果被其他執行緒中斷了,則拋出InterruptedException,
tryLock():只是嘗試獲取鎖,立即回傳,不阻塞,如果獲取成功,回傳true,否則回傳false,
tryLock(long time,TimeUnit unit):先嘗試獲取鎖,如果能成功則立即回傳true,否則阻塞等待,但等待的最長時間由指定的引數設定,在等待的同時回應中斷,如果發生了中斷,拋出InterruptedException,如果在等待的時間內獲得了鎖,回傳true,否則回傳false,
newCondition:新建一個條件,一個Lock可以關聯多個條件
可以看出,相比synchronized,顯式鎖支持以非阻塞方式獲取鎖、可以回應中斷、可以限時,這使得它靈活得多,
ReentrantLock
Lock介面的主要實作類是ReentrantLock,它的基本用法lock/unlock實作了與synchronized一樣的語意
ReentrantLock有兩個構造方法

引數fair表示是否保證公平,不指定的情況下,默認為false,表示不保證公平,所謂公平是指,等待時間最長的執行緒優先獲得鎖,保證公平會影響性能,一般也不需要,所以默認不保證,synchronized鎖也是不保證公平的
使用顯式鎖,一定要記得呼叫unlock,一般而言,應該將lock之后的代碼包裝到try陳述句內,在finally陳述句內釋放鎖,
代碼示例
public void fun() {
lock.lock();
try{
count++;
}finally {
lock.unlock();
}
}
使用tryLock()避免死鎖
使用tryLock(),可以避免死鎖,在持有一個鎖獲取另一個鎖而獲取不到的時候,可以釋放已持有的鎖,給其他執行緒獲取鎖的機會,然后重試獲取所有鎖,
銀行轉賬實體
賬戶類
class Account{
private Lock lock = new ReentrantLock();
private volatile double money;
public Account(double initialMoney){
this.money = initialMoney;
}
public void add(double money){
lock.lock();
try{
this.money = money;
}finally {
lock.unlock();
}
}
public void reduce(double money){
lock.lock();
try{
this.money -= money;
}finally {
lock.unlock();
}
}
public double getMoney() {
return money;
}
public void lock(){
lock.lock();
}
public void unlock(){
lock.unlock();
}
public boolean tryLock(){
return lock.tryLock();
}
}
在賬戶之間轉賬,需要兩個賬戶都鎖定,如果不使用tryLock,而直接使用lock就會發生死鎖,這里可以試一試
class NoEnoughMoney extends Exception {
public static void transfer(Account from,Account to,double money)
throws NoEnoughMoney {
from.lock();
try {
to.lock();
try {
if(from.getMoney() >= money) {
from.reduce(money);
to.add(money);
} else {
throw new NoEnoughMoney();
}
} finally {
to.unlock();
}
} finally {
from.unlock();
}
}
}
模擬死鎖的程序
public static void simulateDeadLock() {
final int accountNum = 10;
final Account[]accounts = new Account[accountNum];
final Random rnd = new Random();
for(int i = 0; i < accountNum; i++) {
accounts[i]= new Account(rnd.nextInt(10000));
}
int threadNum = 100;
Thread[]threads = new Thread[threadNum];
for(int i = 0; i < threadNum; i++) {
threads[i]= new Thread() {
public void run() {
int loopNum = 100;
for(int k = 0; k < loopNum; k++) {
int i = rnd.nextInt(accountNum);
int j = rnd.nextInt(accountNum);
int money = rnd.nextInt(10);
if(i!= j) {
try {
transfer(accounts[i],accounts[j],money);
} catch (NoEnoughMoney e) {
}
}
}
}
};
threads[i].start();
}
}
這里有10個賬戶,100個執行緒,每個執行緒執行100次回圈,在每次回圈中,隨機挑選兩個賬戶進行轉賬,每次執行該段代碼都會發生死鎖,
使用tryLock實作轉賬
public static boolean tryTransfer(Account from,Account to,double money) throws NoEnoughMoney {
if(from.tryLock()) {
try {
if(to.tryLock()) {
try {
if(from.getMoney() >= money) {
from.reduce(money);
to.add(money);
} else {
throw new NoEnoughMoney();
}
return true;
} finally {
to.unlock();
}
}
} finally {
from.unlock();
}
}
return false;
}
如果兩個鎖都能夠獲得,且轉賬成功,則回傳true,否則回傳false,不管怎樣,結束都會釋放所有鎖,
transfer方法可以回圈呼叫該方法以避免死鎖
public static void transfer(Account from,Account to,double money) throws NoEnoughMoney {
boolean success = false;
do {
success = tryTransfer(from,to,money);
if(!success) {
Thread.yield();
}
} while (!success);
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/260365.html
標籤:其他
上一篇:C/C++演算法競賽代碼框架
下一篇:前端內卷加速破局之道
