1. 可重入鎖
1.1ReentrantLock的使用
public class ReentrantLockDemo implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static int count = 0;
@Override
public void run() {
for(int i=0; i<10000; i++) {
//lock.lock();
try {
count ++;
}finally {
//lock.unlock(); //使用ReentrantLock的範式! 放入finally塊中使用。
}
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockDemo th = new ReentrantLockDemo();
Thread t1 = new Thread(th);
Thread t2 = new Thread(th);
t1.start();t2.start();
t1.join();t2.join(); //主線程等待t1結束,主線程等待t2結束
System.out.println(count);
}
}
運行結果:
若恢復鎖:
1.1 ReentrantLock的可重入特性
線程拿了這把鎖多少個許可,就要釋放多少次。若沒有釋放完全,會導致其他線程拿不到許可。
public class ReentrantLockDemo_Re implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static int count = 0;
@Override
public void run() {
for(int i=0; i<10000; i++) {
lock.lock();
lock.lock(); //重入
try {
count ++;
}finally {
lock.unlock(); //必須釋放兩次
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockDemo_Re th = new ReentrantLockDemo_Re();
Thread t1 = new Thread(th);
Thread t2 = new Thread(th);
t1.start();t2.start();
t1.join();t2.join(); //主線程等待t1結束,主線程等待t2結束
System.out.println(count);
}
}
假如只釋放了一次。那麼必然有一個線程會卡在獲取鎖的位置:
卡在了17行,因爲之前線程沒有釋放完,導致第二個線程無法獲取鎖,一直等待:
1.2 ReentrantLock的可中斷特性
通過lockInterruptibly()加鎖,可以在死鎖或者線程長時間卡住的時候,線程調用interrupt()拋出中斷異常。
public class ReentrantLockInterruptibly implements Runnable{
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
//控制加鎖順序,方便構造死鎖
public ReentrantLockInterruptibly(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if(lock == 1) {
lock1.lockInterruptibly(); //獲取鎖1
try {
Thread.sleep(500);
} catch (InterruptedException e) {}
lock2.lockInterruptibly(); //再申請鎖2
}else {
lock2.lockInterruptibly(); //獲取鎖2
try {
Thread.sleep(500);
} catch (InterruptedException e) {}
lock1.lockInterruptibly(); //再申請鎖1
}
}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 {
ReentrantLockInterruptibly r1 = new ReentrantLockInterruptibly(1);
ReentrantLockInterruptibly r2 = new ReentrantLockInterruptibly(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();t2.start();
Thread.sleep(1000);
//中斷其中一個線程
DeadlockChecker.check(); //檢測到死鎖便中斷這個線程
}
/**
* 死鎖檢查類
*/
public class DeadlockChecker {
private final static ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
final static Runnable deadlockCheck = new Runnable() {
@Override
public void run() {
while(true) {
long[] deadlockedThreadIds = mbean.findDeadlockedThreads(); //找到所有死鎖
if (deadlockedThreadIds != null) {
ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds);
for(Thread t : Thread.getAllStackTraces().keySet()) {
for(int i = 0; i < threadInfos.length; i++) {
if(t.getId() == threadInfos[i].getThreadId()) {//如果是死鎖,便中斷
t.interrupt(); //中斷此線程
}
}
}//end of for
}//end of if
try {
Thread.sleep(5000);
}catch(InterruptedException e) {
}
}// end of while
}
};
public static void check() {
Thread t = new Thread(deadlockCheck);
t.setDaemon(true); //跟業務沒有關係,只是做死鎖檢查而已.設爲守護線程
t.start();
}
}
1.3 ReentrantLock的可限時特性
也是一種避免死鎖,或者長期等待的一種措施。
/**
* ReentrantLock可限時特性
* @author liq
*/
public class ReentrantLockTimeLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if(lock.tryLock(5, TimeUnit.SECONDS)) { //限時鎖,超過5秒獲取鎖失敗
Thread.sleep(6000);
}else {
System.out.println("get lock failed");
}
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(lock.isHeldByCurrentThread()) lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockTimeLock timeLock = new ReentrantLockTimeLock();
//必有一個線程獲取鎖失敗,並在5秒後打印"get lock failed"
Thread t1 = new Thread(timeLock);
Thread t2 = new Thread(timeLock);
t1.start();
t2.start();
}
}
5秒後必有一個線程獲取鎖失敗:
1.4 ReentrantLock的公平鎖特性
一般來講,鎖都是非公平的,即,先來的線程不一定先獲取鎖,後來的線程也不一定後獲取鎖。如果先來的線程一直獲取不到鎖,可能會導致飢餓現象。
當然,公平鎖要處理排隊的問題,性能較差。
//源碼:構造方法
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
使用:
public static ReentrantLock fairLock = new ReentrantLock(true);
1.5 ReentrantLock 鎖的實現
//源碼
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1)) //CAS獲取鎖 0狀態:表示沒有人佔用,1狀態:表示鎖已經被佔用
setExclusiveOwnerThread(Thread.currentThread());
else //拿鎖沒有成功
acquire(1);//鎖申請
}
//鎖申請
public final void acquire(int arg) {
if (!tryAcquire(arg) && //試着再獲取一次,說不定剛被人釋放了
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //加入等待隊列當中去,之後再試着取一次
selfInterrupt();
}
//加入到等待隊列
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//每個node對應當前線程
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;//將當前節點放到隊列的尾巴上去
return node;
}
}
enq(node);
return node; //返回當前節點
}
/*unlock()操作*/
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); //等待隊列的頭部進行釋放
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); //進行unpark()操作
}
2. condition 條件
類似於Object.wait() 和 Object.notify();且都需要在同步塊中執行。
Object.wait() 和 Object.notify()結合Synchronized使用,而condition的await()和signal()都需要結合ReentrantLock 搭配使用。
await()
會使當前線程等待,同時釋放當前鎖,當其他線程中使用signal()
時或者signalAll()
方法時,線程會重新獲得鎖並繼續執行。或者當線程被中斷時,也能跳出等待。這和Object.wait()方法很相似。
awaitUninterruptibly()
與await()基本相同,但是它並不會在等待過程中響應中斷。
signal()
方法用於喚醒一個在等待中的線程。相對的signalAll()
方法會喚醒所有在等待中的線程。這和Object.notify()方法很類似。
爲什麼一定要在同步塊中執行(Object.wait() 和 Object.notify(); condition的await()和signal())?
【答】設想這樣一種情況:等待和通知這兩個過程不需要鎖,若,通知(喚醒)發生在了等待之前。此時,程序邏輯認爲它已經發送過了通知,預想着等待被喚醒繼續執行;但實際情況是,此時的等待還沒有發生。直到等待發生後,它一直等待被喚醒。但已經沒有線程喚醒它了,導致產生了死鎖。反過來說,正是由於這種等待和喚醒的雙方在同一個鎖上的協調,才能保證線程間的協調同步。不然,隨意喚醒,隨意等待。喚醒時也不知道有沒有線程在等待,等待時也不知道接下來有沒有線程喚醒自己,豈不是亂了套?
public class ReentrantLockCondition implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock(); //必須在同步塊之間等待
condition.await(); //線程掛起,釋放當前鎖 等待主線程喚醒或被中斷。喚醒後需要先拿到鎖才能繼續向下執行
System.out.println("Thread is going on");
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockCondition tl = new ReentrantLockCondition();
Thread t1 = new Thread(tl);
t1.start();
Thread.sleep(2000);
//通知線程t1繼續執行
lock.lock();
condition.signal(); //喚醒等待在condition上的掛起線程
lock.unlock();
}
}
3. Semaphore 信號量
信號量類似於一種有限的資源。
允許若干個線程進入臨界區,超過許可數量的線程就必須等待。
信號量的許可數量爲1的時候就相當與一把鎖。
主要API:
public void acquire() //獲取信號量
public void release() //釋放信號量
public void acquireUninterruptibly() //等待過程中不響應中斷
public boolean tryAcquire() //僅僅試一下,拿不到就返回false
public boolean tryAcquire(long timeout, TimeUnit unit) //try多長時間
/**
* 信號量演示
* @author liq
*
*/
public class SemaphoreDemo implements Runnable{
final Semaphore semp = new Semaphore(5); //信號量只有5個許可
@Override
public void run() {
try {
semp.acquire(); //當前線程拿到一個許可
//模擬耗時操作
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId() + ":done!");
}catch(InterruptedException e) {
e.printStackTrace();
}finally {
semp.release(); //釋放掉許可
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(20);
final SemaphoreDemo demoTask = new SemaphoreDemo();
for(int i = 0; i < 20; i++) { //一次性提交20個任務, 一次5個信號量允許5個線程做,做完之後釋放掉信號量,其他線程才能拿到許可
exec.submit(demoTask);
}
}
}
4. ReadWriteLock 讀寫鎖
JDK5中提供的讀寫分離鎖
讀-讀不互斥:讀讀之間不阻塞
讀-寫互斥:讀阻塞寫,寫也會阻塞讀
寫-寫互斥:寫寫阻塞
主要接口:
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static readLock = readWriteLock.readLock(); //獲取讀鎖
private static Lock writeLock = readWriteLock.writeLock(); //獲取寫鎖
5. CountDownLatch 倒數計時器
是一種柵欄的機制。一個線程等待其他所有線程完成後繼續。
在火箭發射前,爲了保證萬無一失,往往還要進行各項設備、儀器的檢查。只有等所有檢查完畢後,引擎才能點火。這種場景就非常適合使用CountDownLatch。它可以使得點火線程等待所有檢查線程全部完工後,再執行。
主要接口:
static final CountDownLatch end = new CountDownLatch(10); //有10個檢查項目
end.countDown(); //每個線程做完後,計時減1,減到0後,主線程停止等待。
end.await(); //所有項目(線程等待)
package ch5;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* CountDownLatch演示
* @author liq
*
*/
public class CountDownLatchDemo implements Runnable {
static final CountDownLatch end = new CountDownLatch(10); //10個檢查任務
@Override
public void run() {
try {
//模擬檢查任務
Thread.sleep(new Random().nextInt(10)*1000);
System.out.println("check complete");
end.countDown(); //每次做完一項任務,計數器減1
}catch(InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
CountDownLatchDemo demoTask = new CountDownLatchDemo();
ExecutorService pool = Executors.newFixedThreadPool(10);
for(int i = 0; i < 10; i++) {
pool.submit(demoTask);
}
//等待檢查
end.await(); //計數器減少到0,喚醒
//發射火箭
System.out.println("Fire!");
pool.shutdown();
}
}
6. CyclicBarrier 循環柵欄
計數器可以反覆使用,比如,假設我們將計數器設置爲10。那麼湊齊第一批10個線程後,計數器就會歸0,然後接着湊齊下一批10個線程。
區別於“CountDownLatch 一個線程等待其他線程”,線程之間是相互等待的
主要接口:
public CyclicBarrier(int parties, Runnable barrierAction)//parties :參與者個數,比如10個項目; barrierAction:柵欄動作,就是當計數器一次計數完成後,系統會執行的動作。
await() //等待倒計時完成
package ch5;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 循環柵欄演示
* @author liq
*
*/
public class CyclicBarrierDemo {
public static class Soldier implements Runnable{
private String soldier;
private final CyclicBarrier cyclic;
Soldier(CyclicBarrier cyclic, String soldierName){
this.cyclic = cyclic;
this.soldier = soldierName;
}
@Override
public void run() {
try {
//等待所有士兵到齊
cyclic.await(); //倒計數完成,會執行BarrierRun()動作
doWork();
//等待所有士兵完成工作
cyclic.await(); //循環柵欄體現於此, 又一次使用cyclic柵欄進行倒計數
} catch (InterruptedException e) {
e.printStackTrace();
}catch(BrokenBarrierException e) {
e.printStackTrace();
}
}
void doWork() {
//模擬doWork:休眠隨機時間
try {
Thread.sleep(Math.abs(new Random().nextInt()%10000));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(soldier + ":任務完成");
}
}
public static class BarrierRun implements Runnable{
boolean flag;
int N;
public BarrierRun(boolean flag, int N) {
this.flag = flag;
this.N= N;
}
@Override
public void run() {
if(flag) {//第二次任務完成的時候會執行
System.out.println("司令:[士兵"+ N +"個,完成任務!]");
}else {//第一次集合完畢的時候會執行
System.out.println("司令:[士兵"+ N +"個,集合完畢!]");
flag = true;
}
}
}
public static void main(String[] args) {
final int N = 10; //10個士兵
Thread[] allSoldier = new Thread[N];
boolean flag = false;
CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N)); //10個士兵到達後,執行BarrierRun();
//設置屏障點,主要是爲了執行這個方法
System.out.println("集合隊伍");
for(int i = 0; i < N; ++i) {
System.out.println("士兵"+i+"報道!");
allSoldier[i] = new Thread(new Soldier(cyclic, "士兵" + i));
allSoldier[i].start();
//若有一個線程被中斷了,會拋InterruptedException;其它線程看自己永遠沒有希望繼續,會拋BrokenBarrierException
/* if(i == 5) {
allSoldier[0].interrupt();
}*/
}
}
}
7. LockSupport 鎖支持
提供比較底層的線程阻塞原語。用來將線程掛起,有點像suspend()。但有所不同。LockSupport可以毫無顧忌地使用,suspend()是不建議使用的。如果resume()發生在suspend()之前,那麼,線程將會被永遠掛起。產生”凍結”的效果。永遠醒不過來。
其設計思想類似信號量。根據許可(permit)來喚醒,unpark()發生在park()之前,線程是掛不住的。unpark()能使得一個許可可用。默認許可是不可用的,所以當park()的時候,由於許可不可用,當前線程會被掛起堵住,直到許可可用。(Disables the current thread for thread scheduling purposes unless the permit is available. )。當然,線程也可中斷使得park()返回。park()自身並不拋出異常,但它會響應中斷異常,一旦線程發生中斷,它會立即返回。
主要接口:
LockSupport.park(); //線程掛起
LockSupport.unpark(); //線程恢復
代碼示例:
import java.util.concurrent.locks.LockSupport;
/**
* LockSupport演示
* @author liq
*
*/
public class LockSupportDemo {
public static Object u = new Object();
public static class ChangeObjectThread extends Thread{
public ChangeObjectThread(String name) {
super.setName(name);
}
@Override
public void run() {
synchronized (u) {
System.out.println("in "+getName());
//睡眠1秒,使得unpark()發生在park之前
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park(); //線程掛起
}
}
}
public static void main(String[] args) throws InterruptedException {
ChangeObjectThread t1 = new ChangeObjectThread("t1");
ChangeObjectThread t2 = new ChangeObjectThread("t2");
t1.start();
Thread.sleep(100);
t2.start();
//unpark()發生在park之前, t1, t2 也不會掛住
LockSupport.unpark(t1);
LockSupport.unpark(t2);
t1.join();
t2.join();
}
}
執行結果可見,兩個線程正常執行結束。並沒有掛住。