1. Atomic類和線程同步新機制
這章我們來繼續(xù)將Amotic的問題,然后將除了synchronized之外的鎖。事實上,無鎖化操作比synchronized效率更高。
下面寫個程序分別說明synchronize 和longAdder,Amotic
package com.learn.thread.three;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
public class TestAtomicOrSynOrLongAdder {
static long count1 = 0L;
private static final AtomicLong count2 = new AtomicLong(0L);
private static LongAdder count3 = new LongAdder();
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[1000];
// AtomicLong
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() ->{
for (int k = 0; k < 10000; k++) {
count2.incrementAndGet();
}
});
}
long start = System.currentTimeMillis();
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
long end = System.currentTimeMillis();
System.out.println("Amotice " + count2.get() + "time " + (end - start));
// synchronized Long
Object lock = new Object();
for (int i = 0; i < threads.length; i++) {
threads[i] =
new Thread(() -> {
for (int k = 0; k < 10000; k++) {
synchronized (lock) {
count1 ++;
}
}
});
}
start = System.currentTimeMillis();
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
end = System.currentTimeMillis();
System.out.println("Sync: " + count1 + "time " + (end - start));
// LongAdder
for (int i = 0; i < threads.length; i++) {
threads[i] =
new Thread(() -> {
for (int k = 0; k < 10000; k++) {
count3.increment();
}
});
}
start = System.currentTimeMillis();
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
end = System.currentTimeMillis();
System.out.println("LongAdder: " + count1 + "time " + (end - start));
}
}
至于以上各種“鎖”的效率,要分情況使用。先來看這三種的優(yōu)勢
Amotic和synchronized的對比下,synchronzied有可能要去操作系統(tǒng)申請重量級鎖,所以synchronized的效率是偏低的
LongAdder和Amotic對比,LongAdder的內(nèi)部做了一個分段鎖,類似于分段鎖的概念,在它的內(nèi)部的時候,會把一個值放到一個數(shù)組里,比如說數(shù)組長度為4,最開始是0,1000個線程,250個線程就放在第一個數(shù)組元素里,以此類推,每一個都網(wǎng)上遞增算出來的結(jié)果加在一起。
先來復(fù)習(xí)一下之前將的synchronized的細節(jié)
這是有鎖分級的情況,在一定情況下,synchronized是pian是效率是好的,但是如果升級為重量級鎖,那么效率是低的。
執(zhí)行時間短,要同步的代碼量少,線程數(shù)少用CAS
執(zhí)行時間長,線程數(shù)多,用系統(tǒng)鎖
當(dāng)線程數(shù)為一萬個的時候
2. 復(fù)習(xí)完了synchronized,下面看看基于CAS的一些新型鎖,先來講這些鎖的用法,再來說這些鎖的原理
2.1. ReentrantLock
第一種就是之前講過的可重入鎖ReentrantLock,其實synchronized也是一種可重入鎖,之前講述線程synchronized概論的時候就說過方法鎖里面調(diào)用方法鎖或者說子類和父類synchronized(this)就是同一把鎖,是會使用到同一個鎖的!這就是可重入鎖。
ReentrantLock 是完全可以替代synchronized的,就是把原來寫synchronized的地方換寫成lock.lock(),加完鎖之后需要注意記得lock.unlock解鎖,因為synchronized是自動解鎖的,大括號執(zhí)行完就結(jié)束了,lock不行,lock必須手動解鎖,建議手動解鎖放在try…finally里面保證最好一定要解鎖,不然的話,上鎖之后中間執(zhí)行的過程就有問題了,死在那里,別人就永遠別想拿到鎖了?。?!
package com.learn.thread.three;
import com.learn.thread.second.T;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestReentranLock {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock();
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(2L);
System.out.println(i);
}
}catch (Exception ex) {
} finally {
lock.unlock();
}
}
void m2() {
try {
lock.lock();
System.out.println("m2");
}catch (Exception ex) {
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
TestReentranLock testReentranLock = new TestReentranLock();
new Thread(() -> {
testReentranLock.m1();
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
testReentranLock.m2();
}).start();
}
}
ReentrantLock比synchronized強大的地方就是tryLock進行嘗試鎖定,不管是否鎖定,方法都將繼續(xù)執(zhí)行,synchronized如果搞不定的會,就會阻塞。但是用ReentrantLock你自己就可以決定你到底要不要wait
package com.learn.thread.three;
import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestReentranLockTryLock {
private static Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock();
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1L);
System.out.println(i);
}
}catch (Exception ex) {
} finally {
lock.unlock();
}
}
/**
* 使用tryLock進行嘗試鎖定,不管鎖定與否方法都將繼續(xù)執(zhí)行
* 可以使用返回值來判斷是否鎖定,true表示加鎖成功,false表示枷鎖失敗
* 同樣也可以指定trylock的時間
*/
void m2() {
// 這里不加時間參數(shù),默認是鎖一秒的
boolean locked = lock.tryLock();
System.out.println("m2 ... " + locked);
if (locked) {
System.out.println("我被鎖住了,現(xiàn)在釋放鎖進入m2");
lock.unlock();
}
try {
// 這里只是加鎖的時間,過了5秒以后,鎖釋放
System.out.println(locked);
locked = lock.tryLock(5, TimeUnit.SECONDS);
System.out.println("m2 ....." + locked);
}catch (Exception ex) {
System.out.printf(ex.toString());
} finally {
// 這里如果不去判斷,會異常
if (locked) {
lock.unlock();
}
}
}
public static void main(String[] args) {
TestReentranLockTryLock testReentranLockTryLock = new TestReentranLockTryLock();
new Thread(() -> {
testReentranLockTryLock.m1();
}).start();
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception ex) {
}
new Thread(() -> {
testReentranLockTryLock.m2();
}).start();
}
}
當(dāng)然除了tryLock,還可以用lock.lockInterruptibly對interrupt()做出相應(yīng),可以被打斷的加鎖,比如說我們可以調(diào)用一個t2.interrupt()打斷它的等待,讓它自己可以加鎖。如果有線程1上來加鎖,加鎖以后開始沒完沒了的睡,如果線程1加了鎖,那么線程2就永遠無法得到鎖了,這時候就可以使用interrupt強制打斷線程2的等待,通過異常的形式讓線程2去執(zhí)行。
lockInterruptibly() 方法的作用:如果當(dāng)前線程未被中斷則獲得鎖,如果當(dāng)前線程被中斷則出現(xiàn)異常。
void m4() {
try {
// 強制打斷鎖
lock.lockInterruptibly();
System.out.println("線程2 打斷線程1,開始執(zhí)行");
try {
TimeUnit.SECONDS.sleep(5);
}catch (Exception ex) {
}
System.out.println("線程2 執(zhí)行完成");
}catch (Exception ex) {
System.out.println("線程2被中斷著,可以去完成其他事情");
} finally {
System.out.println(lock.tryLock());
lock.unlock();
}
}
public static void main(String[] args) {
TestReentranLockTryLock testReentranLockTryLock = new TestReentranLockTryLock();
new Thread(() -> {
testReentranLockTryLock.m3();
}).start();
Thread t2 = new Thread(() -> {
testReentranLockTryLock.m4();
});
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception ex) {
}
// t2 如果
t2.interrupt();
}
void m3() {
try {
lock.lock();
System.out.println("線程1 鎖住,無休止的睡眠");
try {
TimeUnit.SECONDS.sleep(100000000);
}catch (Exception ex) {
}
}catch (Exception ex) {
} finally {
lock.unlock();
}
}
ReentrantLock還可以指定公平鎖。公平鎖的意思就是當(dāng)我們new 一個ReentrantLock你可以傳一個參數(shù)為true,這個表示公平鎖,公平鎖的意思是誰等在前面就讓誰先執(zhí)行,而不是后來了就執(zhí)行。ReentrantLock默認是非公平鎖
package com.learn.thread.three;
import com.learn.thread.second.T;
import java.util.concurrent.locks.ReentrantLock;
/**
* 測試公平鎖
*/
public class TestReentrantLockTrue extends Thread{
private static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
for (int i = 0; i< 100; i++) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "獲得鎖");
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
TestReentrantLockTrue testReentrantLockTrue = new TestReentrantLockTrue();
Thread t1 = new Thread(testReentrantLockTrue);
Thread t2 = new Thread(testReentrantLockTrue);
t1.start();
t2.start();
}
}
2.2.回顧ReentrantLock
首先ReentrantLock是可以替代synchronized的,本身底層就是cas
tryLock:自己來控制,控制不住鎖怎么辦
lockInterruptibly: 用異常的形式,取消等待
公平鎖與非公平鎖的等待
以后聊AQS的時候,實際上它內(nèi)部用的是park和unpark,也不是全部cas,也是一個鎖升級的概念,只不過這個鎖升級做的比較隱匿,在你等待這個隊列的時候如果你拿不到還是會進入一個阻塞狀態(tài),前面至少有一個cas狀態(tài),它不像原先就直接進入阻塞狀態(tài)了。
2.3.CountDownLatch
CountDownLatch 叫倒數(shù),Latch是門栓的意思(倒數(shù)的一個門栓, 5 ,4,3,2,1 數(shù)到了,我這個門栓就打開了)
看一下下面這個程序unsingcountDownLatch,new了100個線程,接下來,又來個100個數(shù)量的CountDownLatch,這就是設(shè)置了門栓,記錄個數(shù)為1000,每一個線程結(jié)束的時候就讓latch.countDown(),然后啟動所有的線程,在latch.await(),最后結(jié)束。
latch.countDown()是和latch.await()連用的,countDown是看住門栓,等每個線程執(zhí)行到await()的時候就會按一下CountDown是,讓其在原來的基礎(chǔ)上減1,一直到這個數(shù)字變成0的時候就會被打開,這就是它們的概念,是用來等著線程結(jié)束的
用join實際上不太好控制,必須要你線程結(jié)束了才能控制,但是如果是一個門栓的話我在線程里不聽得CountDown,在一個線程里就可以控制這個門栓什么時候可以往前走,用join我只能是當(dāng)前線程結(jié)束了,你才能自動往前走,用join可以,但是用countDown更加靈活
package com.learn.thread.three;
import com.learn.thread.first.T;
import java.util.concurrent.CountDownLatch;
/**
*
*/
public class TestCountDownLatch {
/**
* 用CountDownLatch控制線程結(jié)束
*/
private static void usingCountDownLacth() {
Thread[] threads = new Thread[100];
CountDownLatch countDownLatch = new CountDownLatch(threads.length);
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
int result = 0;
for (int j = 0; j < 10000; j++) {
result += j;
}
System.out.println(result);
// 看住門栓,每調(diào)用一次就減1
countDownLatch.countDown();
System.out.println("我看住門栓了");
});
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
try {
// 當(dāng)countDown減為0的時候,這里就會執(zhí)行了
countDownLatch.await();
System.out.println("我來減門栓的數(shù)量了");
}catch (Exception ex) {
}
System.out.println("end Latch");
}
/**
* 模擬join 不好控制線程
*/
private static void usingJoin() {
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
int result = 0;
for (int j = 0; j < 10000; j++) {
result += j;
}
System.out.println(result);
});
}
for (int i = 0; i < threads.length; i++) {
System.out.println("線程start");
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
try {
System.out.println("執(zhí)行join");
threads[i].join();
// 這里模擬線程結(jié)束結(jié)束之后的動作
System.out.println("equals countDown.latch");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("end usingJoin");
}
public static void main(String[] args) {
usingJoin();
// usingCountDownLacth();
}
}
2.4.CyclicBarrier
這個名字叫CyclicBarrier,也是一個同步工具,意思就是循環(huán)柵欄,就是什么時候人滿了就把柵欄推到,全部放出去,之后柵欄又重新起來,再來人,滿了,放出去再繼續(xù)。
舉例
CyclicBarrier的概念比如說一個復(fù)雜的操作,需要訪問數(shù)據(jù)庫,需要訪問網(wǎng)絡(luò),需要方位文件。有一種方式是順序執(zhí)行,這事一種非常低的效率,還有一種方式就是并發(fā)的執(zhí)行,用不同的線程去操作,并且是這三個步驟有結(jié)果了我再進行下一次操作,這時候就用到了CyclicBarrier
package com.learn.thread.three;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class TestCyclicBarrier {
private CyclicBarrier cyclicBarrier;
public static void main(String[] args) {
testSout();
}
private static void testSout() {
// 這里會起一個線程去執(zhí)行第二個參數(shù)的內(nèi)容可以為空
CyclicBarrier cyclicBarrier = new CyclicBarrier(20, () -> {
System.out.println("滿人了");
});
for (int i = 0; i < 400; i++) {
new Thread(() -> {
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
2.5.Phaser
Phaser更像結(jié)合了CountDownLatch和CyclicBarrier,中文意思就是階段。
Phaser是按照不同的階段來對線程進行執(zhí)行,就是它本身是維護著一個階段這樣的一個成員變量,比如說第0個階段,第一個階段,每個階段不同的時候這個線程都可以往前走,有的線程走個某個階段就停了,有的線程一直會走到結(jié)束。
你的程序中如果說用到分好幾個階段執(zhí)行,而且有個階段必須得幾個人共同參與的一種情形就可能會用到Phaser
下面我們模擬一個場景結(jié)婚,分成4個階段,分別是到達、吃飯、離開、洞房。首先吃飯之前必須要所有客人到達婚禮線程,離開也是要所用人吃完飯才離開,但是洞房是新郎和新娘的事情,客人們必須離開。
package com.learn.thread.three.marry;
import java.util.Random;
import java.util.concurrent.Phaser;
/**
* 人類,用來模擬每一個階段人的操作
*/
public class Person implements Runnable {
public Person(String name) {
this.name = name;
}
private String name;
private void eat() {
System.out.println(this.name + "吃飯");
// 進入柵欄階段
TestPhaser.phaser.arriveAndAwaitAdvance();
}
private void arrive() {
System.out.println(this.name + "到達婚禮現(xiàn)場");
TestPhaser.phaser.arriveAndAwaitAdvance();
}
private void leave() {
System.out.println(this.name + "離開");
TestPhaser.phaser.arriveAndAwaitAdvance();
}
private void hug() {
if (name.equals("新郎") || "新娘".equals(name)) {
System.out.println(this.name + "洞房了");
TestPhaser.phaser.arriveAndAwaitAdvance();
}else {
// 其他線程都不參與,控制柵欄的個數(shù)
TestPhaser.phaser.arriveAndDeregister();
// 還可以往柵欄上加線程
//TestPhaser.phaser.register();
}
}
@Override
public void run() {
arrive();
eat();
leave();
hug();
}
static class TestPhaser {
static Random random = new Random();
static MarriagePhaser phaser = new MarriagePhaser();
}
static class MarriagePhaser extends Phaser {
/**
* 線程抵達這個柵欄的時候,所有的線程都滿足了這個第一個柵欄的條件了這個方法
* 會被自動調(diào)用
*
* @param phase 第幾個階段,從0開始
* @param registeredParties 這個階段有多少線程參與
* @return
*/
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase) {
case 0:
System.out.println(phase + "所有人都到齊了~" + registeredParties);
return false;
case 1:
System.out.println(phase + "所有人都吃完飯了~" + registeredParties);
return false;
case 2:
System.out.println(phase + "所有人都離開了~" + registeredParties);
return false;
case 3:
System.out.println(phase + "婚禮結(jié)束~ 新浪和新娘抱抱" + registeredParties);
return true;
default:
return true;
}
}
}
public static void main(String[] args) {
TestPhaser.phaser.bulkRegister(7);
for (int i = 0; i < 5; i++) {
new Thread(new Person("p" + i)).start();
}
new Thread(new Person("新郎")).start();
new Thread(new Person("新娘")).start();
}
}
分析上面的程序,我們可以看到phaser.arriveAndAwaitAdvance方法是在進入柵欄前停駐,等線程的數(shù)量達到了就會自動調(diào)用onAdvance方法,返回false說明不是最后的階段,返回true就是說到達了最后的階段。最后phaser.arriveAndDeregister方法是注銷線程,讓線程不再參與階段的執(zhí)行。
2.6.ReadWriteLock讀寫鎖
讀寫鎖的本質(zhì)就是共享鎖和排他鎖,讀鎖就是共享鎖,寫鎖就是排他鎖,讀寫有很多種情況,比如說你的數(shù)據(jù)庫里某條數(shù)據(jù)你放在內(nèi)存里讀的特別多,但是改的時候并不多。
我們先來自己定義一套讀寫鎖
假設(shè)有兩個方法,一個read方法,一個write方法,read的時候我需要往里頭傳一把鎖,這個鎖我們自己定,可以是排他鎖,也可以讀鎖或者寫鎖,write的時候同樣需要傳這把鎖,同時你傳一個新值,在這里值里面?zhèn)饕粋€內(nèi)容。我們模擬這個操作,讀的是一個Int類型的值,讀的時候上鎖,設(shè)置一秒鐘,完了之后read over 最后unlock,然后寫鎖,鎖定之后睡1000秒,然后把新的值給value,write over之后解鎖。
我們可以用之前的ReentrantLock進行加鎖,分析一下這種情況,第一種方式就是直接new ReentrantLock傳進去,主程序定義了一個Runnable對象,第一個是調(diào)用read方法,第二個是調(diào)用write方法同時往里邊扔一個隨機值,然后啟18個讀線程,啟2個寫線程,這個兩個我要執(zhí)行完的話,因為是用了ReentrantLock加鎖,鎖的一秒鐘內(nèi)不沒有任何線程可以拿到鎖,每一個線程執(zhí)行完都要1秒鐘,那么20個線程就需要20秒。
我們完全可以按功能加鎖
上述無非兩種功能,讀和寫,那么能不能讀的時候,所有讀操作都可以共享這把鎖,寫的時候不讓讀呢?ReentrantReadWirteLock是ReentrantLock的一種實現(xiàn),可以實現(xiàn)上述的思想。它能分出兩把鎖,一把readLock,一把writeLock。這兩把鎖在我讀的時候扔進去,因此,18個線程讀是可以在一秒鐘完成工作的,所以讀寫鎖效率會大大提高
下面我們看看兩種方法的效率
package com.learn.thread.three.ReentrantReadWriteLock;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestReentrantReadWriteLock {
static Lock lock = new ReentrantLock();
private static int value = 10;
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static Lock readLock = readWriteLock.readLock();
static Lock wirteLock = readWriteLock.writeLock();
/**
* 普通方式加鎖
*
* @param lock 鎖
*/
public static void read(Lock lock) {
try {
lock.lock();
Thread.sleep(1000);
System.out.println("取值" + value);
System.out.println("read over");
} catch (Exception exception) {
} finally {
lock.unlock();
}
}
/**
* 寫鎖
*
* @param lock 鎖
* @param v 新值
*/
public static void write(Lock lock, int v) {
try {
lock.lock();
Thread.sleep(1);
value = v;
System.out.println("寫了" + value);
} catch (Exception ex) {
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
testNoReadAndWriteLock();
//testReadAndWriteLock();
}
static void testNoReadAndWriteLock() {
Runnable runnable = () -> read(lock);
Runnable write = () -> write(lock, new Random().nextInt());
for (int i = 0; i < 18; i++) {
new Thread(runnable).start();
}
for (int i = 0; i < 2; i++) {
new Thread(write).start();
}
}
static void testReadAndWriteLock() {
Runnable runnable = () -> read(readLock);
Runnable write = () -> write(wirteLock, new Random().nextInt());
for (int i = 0; i < 18; i++) {
new Thread(runnable).start();
}
for (int i = 0; i < 2; i++) {
new Thread(write).start();
}
}
}
第一種效率明顯上比第二種慢多了
以后還寫不寫synchronized?分布式鎖怎么實現(xiàn)的?
以后一般都不會用這些新鎖,多數(shù)用到synchronized,只有特別特別追求效率的時候才用到這些新的鎖,現(xiàn)在的分布式鎖很多,主要有redis和ZooKeeper都可以實現(xiàn)分布式鎖,數(shù)據(jù)庫也可以實現(xiàn),但是數(shù)據(jù)庫實現(xiàn)效率就低了。
給大家講一個簡單的例子,就說秒殺這個事情,在開始秒殺之前它會從數(shù)據(jù)庫讀取某一個數(shù)據(jù),比如電視機500臺,只能最多銷售500臺,完成這件事情是前面的線程訪問同一個數(shù),最開始是0一直漲到500就結(jié)束,需要加鎖,從0遞增。如果是單機的,LongAdder和AtomicIntegr就可以搞定。如果是分布式的,對一個數(shù)進行上鎖,redis是單線程的,所以扔在一臺機器上就ok。
2.7.Semaphore
詞面意思就是信號燈,可以往里邊傳一個數(shù),permits是允許的數(shù)量,你可以想著有幾個信號燈,燈閃爍著數(shù)字表示到底允許幾個來參考我這個信號燈。
s.acquire()這個方法叫做阻塞方法,阻塞方法的意思說我大概acquire不到的話我就停在這里。acquire的意思就是得到,如果我Semaphore s = new Semaphore(1)寫的是1,我取一下,acquire一下他就變成0,當(dāng)變成0之后,別人是acquired不到的,然后繼續(xù)執(zhí)行,線程結(jié)束之后注意要s.release(),執(zhí)行完該執(zhí)行的時候就把他release掉,release又把0變回去1,還原化。
Semaphore的含義也是限流,比如說你在買票,Semaphore寫5,就是說只能5個人同時買票。acquire的意思叫獲取這把鎖,線程如果想繼續(xù)往下執(zhí)行,必須得從Semaphore里獲取一個許可,他一共有5個許可用到了0你就得給我等著。
下面舉例一個場景
例如,有一個八條車道的機動車道,這里只有兩個收費站,到這里,誰acquire得到其中某一個誰執(zhí)行。
默認Semaphore是非公平的,new Semaphore(2, true)第二個值傳true才是設(shè)置公平,公平這個事情是有一堆隊列在哪兒等,大家伙過來排隊。用車道和收費站來舉例子,就是我們有四輛車都在等著進一個車道,當(dāng)后面再來一輛車的時候,它不會抄到前面去,這才叫公平,所以說內(nèi)部是有隊列的,不僅內(nèi)部是有隊列的,本章所講的ReentrantLock,CountDownLatch,CyclicBarrier,Phaser,ReadWriteLock,Semaphore還有后邊講到Exchanger都是用同一個隊列,同一個類實現(xiàn)的,這個類叫做AQS。
package com.learn.thread.three;
import java.util.concurrent.Semaphore;
public class TestSemaphore {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3, true);
new Thread(() -> {
try {
// 阻塞方法,如果線程acquire不到,就停在這里,等別的線程釋放
semaphore.acquire();
System.out.println("t1 running");
Thread.sleep(1000);
System.out.println("t1 ending");
} catch (Exception ex) {
ex.printStackTrace();
} finally {
semaphore.release();
}
}).start();
new Thread(() -> {
try {
semaphore.acquire();
System.out.println("t2 running");
Thread.sleep(1000);
System.out.println("t2 ending");
} catch (Exception ex) {
ex.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
2.8.Exchanger
這個Exchanger叫做交換器,是兩個線程互相交換數(shù)據(jù)用的。比如說第一個線程有一個成員變量s,然后exchanger.exchange(s),第二個也是這樣,t1線程名字叫t1,第二個線程名字叫t2,到最后,打印出來你會發(fā)現(xiàn)他們兩的數(shù)據(jù)交換了。線程間通信的方式非常多,這只是其中的一種,就是線程之間交換數(shù)據(jù)用的。
Exchanger 你可以想象成一個容器,這個容器有兩個值,兩個線程,兩個格的位置,第一個線程執(zhí)行到exchanger.exchange的時候,阻塞。但是要注意我這個exchange方法的時候是往里面扔了一個值,你可以認為把t1扔到第一個格子了,然后第二個線程開始執(zhí)行,也執(zhí)行到exchange方法了,把t2扔到第二個格子里,接下來兩個線程交換了一下,t1扔給t2,t2扔給了t1,兩個線程繼續(xù)往前跑。Exchanger只能是兩個線程之間,交換一個東西只能兩兩進行
下面舉一個游戲中兩個人狀態(tài)交換
package com.learn.thread.three;
import java.util.concurrent.Exchanger;
public class TestExchanger {
static Exchanger<String> exchanger = new Exchanger<>();
public static void main(String[] args) {
new Thread(() -> {
String s = "t1";
try {
s = exchanger.exchange(s);
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s);
},"線程1").start();
new Thread(() -> {
String s = "t2";
try {
s = exchanger.exchange(s);
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s);
},"線程2").start();
}
}