1. 公平鎖和非公平鎖
- 定義:
- 公平鎖:多個(gè)線程按照申請鎖的順序來獲取鎖,按照FIFO規(guī)則從等待隊(duì)列中拿到等待線程獲取相應(yīng)鎖
- 非公平鎖:多個(gè)線程并不是按照申請鎖的順序來獲取鎖,有可能出現(xiàn)后申請鎖的線程先申請到鎖崔泵。在高
并發(fā)環(huán)境下,非公平鎖有可能造成 優(yōu)先級反轉(zhuǎn) 或者 饑餓 的現(xiàn)象。如果非公平鎖搶占失敗,就要繼續(xù)采取類似公平鎖的機(jī)制癌蚁。非公平鎖的優(yōu)點(diǎn)在于吞吐量大。
- 常見的非公平鎖:
- ReentrantLock可以通過指定構(gòu)造函數(shù)的boolean類型來獲取公平/非公平鎖兜畸,默認(rèn)情況下是非公平鎖
- 對于Synchronized而言努释,也是一種非公平鎖
2 可重入鎖(遞歸鎖)
- 定義:可重入鎖的定義要類比遞歸的定義來理解。指在同一個(gè)線程外層函數(shù)獲得鎖之后咬摇,內(nèi)層遞歸函數(shù)仍然能夠獲取該鎖的代碼伐蒂,
即進(jìn)入內(nèi)層函數(shù)時(shí)會自動獲取鎖。也就是說肛鹏,線程可以進(jìn)入任何一個(gè)它已經(jīng)擁有的鎖所同步的代碼塊逸邦。一個(gè)同步方法內(nèi)部仍然存在一個(gè)同步方法,那么可以進(jìn)入內(nèi)層同步方法在扰,且內(nèi)存同步方法和外層同步方法持有的是同一把鎖缕减。
具體看一個(gè)案例來理解可重入鎖:synchronized就是可重入鎖,現(xiàn)在問題是synchronized塊中能夠使用System.out.println()方法健田?
public void println(String x) {
// println方法內(nèi)部使用了synchronized
synchronized (this) {
print(x);
newLine();
}
}
/**
* 演示可重入鎖
*
* @author sherman
*/
public class LockReentrantDemo1 {
public static void main(String[] args) {
// 程序正常運(yùn)行輸出:hello
new LockReentrantDemo1().lockReentrant();
}
public synchronized void lockReentrant() {
/**
* 注意這個(gè)println方法內(nèi)部就使用了synchronized關(guān)鍵字烛卧,鎖住了this
* 即synchronized塊中仍然能夠使用synchronized關(guān)鍵字 -> 可重入的
*/
System.out.println("hello");
}
}
可重入鎖的意義有一點(diǎn)類似于事務(wù)的傳播行為(一個(gè)方法運(yùn)行在另一個(gè)開啟事務(wù)的方法中,那么當(dāng)前方法的事務(wù)行為是什么樣的妓局?)总放,類比來說可重入鎖意義就是:一個(gè)synchronized(鎖)塊運(yùn)行在另一個(gè)synchronized(塊)中,那么當(dāng)前synchronized的具體表現(xiàn)行為是什么好爬,是直接中斷局雄?還是阻塞等待?又或者是正常執(zhí)行存炮,因?yàn)閮蓚€(gè)synchronized鎖住的是同一個(gè)對象炬搭?
可重入鎖的含義就是最后一種:正常執(zhí)行,因?yàn)榭芍厝腈i穆桂,鎖的是同一個(gè)對象宫盔。
- 典型的可重入鎖:ReentrantLock & synchronized關(guān)鍵字
- 作用:最大作用就是防止死鎖,因?yàn)槎鄬忧短椎逆i享完,其實(shí)鎖的是同一個(gè)對象灼芭,另一個(gè)含義就是:嵌套方法持有的是同一把鎖
具體示例:
/**
* 可重入鎖演示
*
* @author sherman
*/
// 演示ReentrantLock是可重入的
class ShareResouce implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
private void get() {
lock.lock();
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + ": get()");
set();
} finally {
lock.unlock();
}
}
private void set() {
lock.lock();
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + ": set()");
} finally {
lock.unlock();
}
}
}
public class LockReentrantDemo2 {
// outer()和inner()方法演示synchronized是可重入的
private synchronized void outer() {
System.out.println(Thread.currentThread().getName() + ": outer method()");
inner();
}
// outer()和inner()方法演示synchronized是可重入的
private synchronized void inner() {
System.out.println(Thread.currentThread().getName() + ": inner method()");
}
public static void main(String[] args) {
// 驗(yàn)證synchronized是可重入的
LockReentrantDemo2 lrd = new LockReentrantDemo2();
new Thread(lrd::outer, "thread-1").start();
new Thread(lrd::outer, "thread-2").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 驗(yàn)證ReentrantLock是可重入的
System.out.println("===================");
new Thread(new ShareResouce(), "thread-3").start();
new Thread(new ShareResouce(), "thread-4").start();
}
}
補(bǔ)充:
在使用ReentrantLock類演示可重入鎖時(shí),lock.lock()和lock.unlock()數(shù)量一定要匹配般又,否則:
- 當(dāng)lock.lock()數(shù)量 > lock.unlock():程序一直運(yùn)行
- 當(dāng)lock.lock()數(shù)量 < lock.unlock():拋出java.lang.IllegalMonitorStateException異常
3 自旋鎖(SpinLock)
自旋鎖嘗試獲取鎖的線程不會立即阻塞彼绷,而是采用循環(huán)的方式去嘗試獲取鎖,這樣的好處是避免線程上下文切換的消耗茴迁,缺點(diǎn)是如果一直自旋會消耗CPU:
/**
* 自旋鎖演示
*
* @author sherman
*/
public class LockSpin {
AtomicReference<Thread> ar = new AtomicReference<>();
private void lock() {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + ": come in!");
while (!ar.compareAndSet(null, thread)) {
}
}
private void unlock() {
Thread thread = Thread.currentThread();
ar.compareAndSet(thread, null);
System.out.println(thread.getName() + ": get out!");
}
public static void main(String[] args) throws InterruptedException {
LockSpin lockSpin = new LockSpin();
new Thread(() -> {
lockSpin.lock();
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lockSpin.unlock();
}, "線程A").start();
// 保證線程A先進(jìn)行獲取到鎖寄悯,讓線程B之后自旋
Thread.sleep(1000);
new Thread(() -> {
lockSpin.lock();
lockSpin.unlock();
}, "線程B").start();
}
}
4 讀寫鎖
寫鎖(獨(dú)占鎖):指該鎖一次只能被一個(gè)線程所持有,ReentrantLock和Synchronized都是獨(dú)占鎖
讀鎖(共享鎖):指該鎖可以被多個(gè)線程所持有
-
讀鎖的共享鎖可保證并發(fā)讀是非常高效的堕义,讀寫猜旬、寫讀、寫寫的過程都是互斥的
/**
演示讀寫鎖
-
@author sherman
*/
class Cache {
private volatile HashMap<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();public Object get(String key) {
lock.readLock().lock();
Object res = null;
try {
System.out.println(Thread.currentThread().getName() + ": 正在讀取+++");
Thread.sleep(100);
res = map.get(key);
System.out.println(Thread.currentThread().getName() + ": 讀取完成---");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
return res;
}public void put(String key, Object value) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + ": 正在寫入>>>");
Thread.sleep(1000);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + ":寫入完成<<<");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
}
public class LockReadWrite {
public static void main(String[] args) {
Cache cache = new Cache();// 寫入操作是被一個(gè)線程獨(dú)占的倦卖,一旦寫線程開始 // 其它線程必須等待其完成后才能繼續(xù)執(zhí)行 for (int i = 0; i < 10; i++) { final int tmp = i; new Thread(() -> cache.put(tmp + "", tmp + ""), String.valueOf(i)).start(); } // 讀操作可以被多個(gè)線程持有 // 其它線程不必等待當(dāng)前讀操作完成才操作 for (int i = 0; i < 10; i++) { final int tmp = i; new Thread(() -> cache.get(tmp + ""), String.valueOf(i)).start(); } }
}
5 CountDownLatch
CountDownLatch是一個(gè)計(jì)數(shù)器閉鎖昔馋,它通過一個(gè)初始化定時(shí)器latch,在latch的值被減到0之前糖耸,其它線程都會被await()方法阻塞秘遏。
以模擬火箭發(fā)射過程解釋CountDownLatch使用:
/**
* CountDownLatch模擬火箭發(fā)射過程:
* 火箭發(fā)射之前需要十個(gè)線程進(jìn)行前期檢查工作,每個(gè)線程耗時(shí)0-4s嘉竟,
* 只有10個(gè)線程對應(yīng)的檢查工作全部完成后邦危,火箭才能發(fā)射
*
* @author sherman
*/
public class CountDownLatchDemo implements Runnable {
public static final int TASK_NUMBERS = 10;
private static CountDownLatch cdl = new CountDownLatch(TASK_NUMBERS);
public static void main(String[] args) throws InterruptedException {
CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.submit(countDownLatchDemo);
}
cdl.await();
System.out.println("檢查工作檢查完畢:fire!");
executorService.shutdown();
}
@Override
public void run() {
try {
// 模擬火箭發(fā)射前的各種檢查工作
int millis = new Random().nextInt(5000);
Thread.sleep(millis);
System.out.println(Thread.currentThread().getName() + ":檢查完畢! 耗時(shí):" + millis + "ms");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 每次檢查完畢后都將計(jì)數(shù)器減1
cdl.countDown();
}
}
}
6 CyclicBarrier
CyclicBarrier是可循環(huán)使用的屏障,它的功能是:讓一組線程到達(dá)一個(gè)屏障(同步點(diǎn))時(shí)被阻塞舍扰,直到最后一個(gè)線程到達(dá)屏障時(shí)倦蚪,屏障才會被打開,所有被屏障阻塞的方法都會被打開边苹。
A synchronization aid that allows a set of threads to all wait for each other to reach a **common
barrier point**. CyclicBarriers are useful in programs involving a fixed sized party of threads that
must occasionally wait for each other. The barrier is called cyclic because it can be re-used
after the waiting threads are released.
示例:模擬集齊七顆龍珠才能召喚神龍:
/**
* CyclicBarrier模擬集齊七顆龍珠才能召喚神龍
* 設(shè)置common barrier point為7陵且,每個(gè)線程收集到七顆龍珠之前都會被阻塞
* 每個(gè)線程都到達(dá)common barrier point時(shí)候才會召喚神龍
*
* @author sherman
*/
public class CyclicBarrierDemo implements Runnable {
private static CyclicBarrier cb = new CyclicBarrier(7, () -> System.out.println("召喚神龍"));
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + ": 到達(dá)同步點(diǎn)(收集到一個(gè)龍珠)!");
cb.await();
System.out.println(Thread.currentThread().getName() + ": 阻塞結(jié)束,繼續(xù)執(zhí)行!");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
CyclicBarrierDemo cbd = new CyclicBarrierDemo();
ExecutorService executorService = Executors.newFixedThreadPool(7);
for (int i = 0; i < 7; i++) {
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.submit(cbd);
}
executorService.shutdown();
}
}
7 Semaphore
Semaphore信號量主要有兩個(gè)目的:
- 用于多個(gè)共享資源的互斥使用;
- 用于并發(fā)數(shù)量的控制(是synchronized的加強(qiáng)版慕购,當(dāng)并發(fā)數(shù)量為1時(shí)就退化成synchronized)聊疲;
主要方法:
- Semaphore(int permits):構(gòu)造函數(shù),允許控制的并發(fā)數(shù)量沪悲;
- acquire():請求一個(gè)信號量获洲,導(dǎo)致信號量的數(shù)量減1;
- release():釋放一個(gè)信號量殿如,信號量加1贡珊;
示例:使用Semaphore模擬請車位過程(3個(gè)車位,10輛車):
/**
* 使用Semaphore模擬搶車位過程(3個(gè)車位涉馁,10輛車)
* 任意時(shí)刻只有3輛車持有線程
*
* @author sherman
*/
public class SemaphoreDemo {
public static void main(String[] args) {
// 模擬三個(gè)車位门岔,十輛車
// 任意時(shí)刻只有三輛車持有車位
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + ": 搶到車位");
// 每輛車占有車位[3,8]秒時(shí)間
Thread.sleep((new Random().nextInt(6) + 3) * 1000);
System.out.println(Thread.currentThread().getName() + ": 釋放車位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}