JDK併發包(JUC)
Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,爲每個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。
重入鎖 (java.util.concurrent.locks.ReentrantLock)
package Test1;
import java.util.concurrent.locks.ReentrantLock;
public class Demo implements Runnable {
// 重入鎖
public static ReentrantLock lock = new ReentrantLock();
static int j = 0;
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
// 獲取鎖
lock.lock();
try {
j++;
} finally {
// 釋放鎖
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Demo tl = new Demo();
Thread t1 = new Thread(tl);
Thread t2 = new Thread(tl);
t1.start();
t1.join();
t2.start();
t2.join();
System.out.println(j);
}
}
觀察上述代碼可以發現,和synchronized相比,重入鎖有着顯式的操作過程,必須指定何時加鎖,何時釋放鎖所以重入鎖對邏輯的控制要遠遠好於synchronized。但是在退出臨界區時必須釋放鎖。
重入鎖允許一個線程連續兩次獲得同一把鎖。如果不允許的話那麼同一個線程在第二次獲得鎖時將會和自己產生死鎖。注意的是加鎖的次數和釋放鎖的次數必須相同,如果釋放鎖的次數過多將會獲得一個IllegalMonitorStateException。
// 獲取鎖
lock.lock();
lock.lock();
try {
j++;
} finally {
// 釋放鎖
lock.unlock();
lock.unlock();
}
中斷響應
對於synchornized來說,如果一個線程在等待鎖。那麼它要麼獲取鎖繼續執行要麼保持繼續等待。而重入鎖提供了另外一種可能,就是線程可以中斷。比如你和同學約好去打球,你等了半小時,突然接到一個電話他不能來了,那麼你就只能回去了。假如一個線程正在等待鎖那麼他依然可以收到一個通知,被告知無需等待,可以停止工作了。這對於處理死鎖有一定的幫助。
package Test1;
import java.util.concurrent.locks.ReentrantLock;
//中斷響應
public class Demo7 implements Runnable {
private int lock;
// 創建重入鎖
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
public Demo7(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (lock == 1) {
// 如果當前線程未中斷則獲取鎖
lock1.lockInterruptibly();
Thread.sleep(1000);
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly();
Thread.sleep(1000);
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 查詢當前線程是否持有鎖
if (lock1.isHeldByCurrentThread())
lock1.unlock();
if (lock2.isHeldByCurrentThread())
lock2.unlock();
System.out.println(Thread.currentThread().getId() + ":線程退出");
}
}
public static void main(String[] args) throws InterruptedException {
Demo7 r1 = new Demo7(1);
Demo7 r2 = new Demo7(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(2000);
// 中斷一個線程
t2.interrupt();
}
}
在這裏我們構建了一個死鎖的程序,當主線程在睡眠的時候,線程t1先佔用lock1,再佔用lock2.而線程t2先佔用lock2,再去請求lock1,導致死鎖。最後我們中斷了線程t2,t2放棄對lock1的請求,同時釋放lock2,t1請求lock2成功。執行完畢。
鎖申請等待時限
除了等待外部通知以外,要避免死鎖還有一種方法就是限時等待。通常一個線程無法拿到鎖我們無法判斷是死鎖了還是產生了飢餓。但是如果我們給定一個等待的時間讓線程來自動放棄是有意義的。使用tryLock()進行一次限時等待。
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
//第一個參數是表示等待時常,第二個參數表示計時單位
public class TimeLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
TimeLock tl = new TimeLock();
Thread t1 = new Thread(tl);
Thread t2 = new Thread(tl);
t1.start();
t2.start();
}
@Override
public void run() {
//接收兩個參數一個表示等待時常一個表示計時單位。
//在這裏由於佔用鎖的線程會持有鎖的時常長達6秒 而另一個線程無法在五秒的等帶時間獲取鎖 請求鎖失敗
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
Thread.sleep(6000);
} else {
System.out.println("get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
if(lock.isHeldByCurrentThread())
lock.unlock();
}
}
}
無參的示例
package Test1;
import java.util.concurrent.locks.ReentrantLock;
public class TryLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public TryLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
if (lock == 1) {
while (true) {
if (lock1.tryLock()) {
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
if (lock2.tryLock()) {
try {
System.out.println(Thread.currentThread().getId() + ":退出");
return;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
}
} else {
while (true) {
if (lock2.tryLock()) {
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
if (lock1.tryLock()) {
try {
System.out.println(Thread.currentThread().getId() + ":退出");
return;
} finally {
lock1.unlock();
}
}
} finally {
lock2.unlock();
}
}
}
}
}
public static void main(String[] args) {
TryLock r1 = new TryLock(1);
TryLock r2 = new TryLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
無參的情況下,當前線程會嘗試會嘗試獲得鎖,如果鎖未被其他線程佔用,則鎖會申請成功,返回true.如果鎖被其他線程佔用則當前線程不會等待立即返回false.這種模式不會引起線程等待,所以不會產生死鎖。
公平鎖
公平鎖的最大特點是不會產生飢餓現象,只要排隊最終是可以得到資源的。如果使用synchronized關鍵字,那麼產生的鎖就是非公平的鎖。重入鎖允許我們對其進行公平性設置。
public ReentrantLock(boolean fair) 當參數設置爲true時,表示鎖是公平的。但是要實現一個公平鎖系統必須維護一個有序隊列,所以成本比較高,性能也相對比較底下。如果沒有特別的需求,不需要使用公平鎖。公平鎖和非公平鎖在線程調度上的表現也是非常不一樣的。
package Test1;
import java.util.concurrent.locks.ReentrantLock;
public class FairLock implements Runnable {
// 公平鎖
public ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
FairLock r = new FairLock();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
@Override
public void run() {
while (true) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + ":獲得鎖");
} finally {
lock.unlock();
}
}
}
}
可以看出如果使用公平鎖線程基本上交替獲得鎖的,幾乎不會發生同一個線程連續多次獲得鎖的情況。從而公平性獲得保證。如果使用非公平鎖,情況會大不相同。
重入鎖重要的api
1.lock() //獲得鎖 如果鎖被佔用則等待
2.lockInterruptibly() //獲得鎖,但是優先響應中斷
3.boolean tryLock() //僅在調用時鎖爲空閒狀態才獲取該鎖。如果鎖可用,則獲取鎖,並立即返回值 true。如果鎖不可用,則此方法將立即返回值 false。
4.tryLock(long time, TimeUnit unit) //在給定的時間內嘗試獲得鎖
5.unlock() //釋放鎖
6. isHeldByCurrentThread() //查詢當前線程是否保持此鎖
重入鎖的實現
1.原子態
2.等待隊列,所有沒有請求到鎖的線程,會進入到等待隊列,等到有線程釋放鎖之後,系統從等待隊列中喚醒一個線程。
3.阻塞原語park()和unpark() 掛起和恢復
該隨筆主要閱讀《Java高併發程序設計》總結