大數據學習歷程
NIO
IO分類
- BIO:BlockingIO,同步式阻塞式IO,即傳統的IO,是Java中最早期的流
- NIO:Non-BlockingIO,又稱New IO,同步式非阻塞式IO,是JDK1.4提供的流
- AIO:AsynchronousIO,異步式非阻塞式IO,可以認爲是NIO的二代版本,是JDK1.8提供的流
BIO的缺點
- 一對一的連接方式:即每一個連接請求需要對應一個線程,在請求量大的情況下,會導致服務器端的壓力非常大從而致使整個服務器的處理效率變低
- 阻塞:當線程在進行read或者write的時候,除非讀完或者寫完,否則在這個過程中不能發生任何操作
- 單向傳輸:數據只能從一端傳向另一端,如果需要反向傳輸需要令創建流對象
NIO的特點
- 一對多的連接方式:利用一個或者少量線程處理大量的連接請求,降低服務器端的壓力
- 非阻塞:在線程不能進行read或者write方法的時候,立即返回0,等待下一次操作
- 雙向傳輸:利用通道可以實現數據的雙向
NIO的缺點
- 在請求量比較大的情況下會出現部分請求的響應時間比較長的現象
- 不適用於長任務場景,不然會導致其他的請求無法處理
BIO和NIO的比較
BIO | NIO |
---|---|
同步阻塞 | 同步非阻塞 |
單向傳輸數據 | 可以雙向傳輸數據 |
一對一的連接方式 | 一對多的連接方式 |
面向流操作 | 面向緩衝區操作 |
適合於請求少、長連接場景 | 適合於大量請求、短連接的場景 |
3大組件
a. Buffer - 緩衝區 - 用於存儲數據
b. Channel - 通道 - 用於傳輸數據
c. Selector - 多路複用選擇器 - 限流
-
Buffer
- 緩衝區,用於存儲數據
- 底層依靠數組來存儲數據,並且存儲的數據是基本類型,但是其中沒有針對boolean類型的緩衝區
- 最常使用的是ByteBuffer,底層依靠的是字節數組
- 重要的位置:capacity >= limit >= position >= mark
a. capacity:容量位。用於標記緩衝區的容量
b. limit:限制位。用於限制操作位所能達到的最大下標,默認和capacity一致
c. position:操作位。用於指向要讀寫的位置,默認爲0
d. mark:標記位。一般用於進行標記,表示之前的數據是無錯的。mark默認爲-1,表示默認不啓用 - 重要操作:
a. flip:翻轉緩衝區。將limit挪到position,然後將position歸零,並且將mark置爲-1
b. clear:清空緩衝區。position歸零,limit挪到capacity,mark置爲-1
c. reset:重置緩衝區。將position挪到mark上
d. rewind:重繞緩衝區。將position歸零,將mark置爲-1
e.hasRemaining: 判斷position 是否小於 limit
f.get: 獲取當前position位字節
g.array: 獲取當前buffer中所有字節
-
Channel
- 通道,用於進行數據的傳輸
- 在傳輸數據的時候,面向緩衝區進行操作的
- File:FileChannel
UDP:Datagra mChannel
TCP:SocketChannel、ServerSocketChannel - 默認是阻塞的,需要手動設置爲非阻塞,設置方法: configureBlocking(false);
- 重要操作:
a. connect:客戶端發起連接。參數InetSocketAddress繼承了SocketAddress
b. bind:服務端綁定端口。
c. configureBlocking:設置爲非阻塞
-
Selector
- 多路複用選擇器,針對通道進行選擇
- 選擇器所針對的通道必須是非阻塞的
- 選擇器在進行選擇時候,選擇通道身上的事件來進行處理
- 重要操作:
a. ServerSocketChannel.register:將通道註冊到ServerSocketChannel上,參數:(Selector,SelectionKey.OP_ACCEPT)
b. Selector.select:篩選出有用的請求。
c. selectedKeys:獲取請求所對應的事件
Concurrent
JDK1.5出現的專門應對高併發的包
主要包含五個內容
BlockingQueue 阻塞隊列
-
概述
- BlockqingQueue是阻塞式隊列中的頂級接口,需要使用它的實現之一來使用 BlockingQueue
- BlockingQueue是有指定界限的
- 在隊列爲空的時候進行獲取操作會產生阻塞
- 在隊列已滿的情況下繼續存儲元素會產生阻塞
- 遵循先進先出(FIFO)的原則
- 適用於生產消費模型,即一個線程生產對象,而另外一個線程消費這些對象的場景
- 無法向一個 BlockingQueue 中插入 null。如果你試圖插入 null,BlockingQueue 將會拋出一個 NullPointerException。
-
重要方法
發生情景 | 拋出異常 | 返回特殊值 | 阻塞 | 定時阻塞 |
---|---|---|---|---|
隊列已滿時,添加 | add(o) | offer(o) | put(o) | offer(o, time, unit) |
隊列爲空時,獲取 | remove() | poll() | take() | poll(time, unit) |
檢索隊列頭元素 | element() | peek() |
ArrayBlockingQueue - 阻塞式順序隊列
1. 在使用的時候需要指定容量/界限,且容量指定之後不可改變
2. 容量在指定之後不能改動
3. 遵循先進先出(FIFO)的原則
//創建時指定容量爲5,隊列中最大允許存儲5個元素,且使用過程中不允許改變容量界限
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
LinkedBlockingQueue - 阻塞式鏈式隊列
1. 在使用的時候可以指定容量也可以不指定容量
2. 如果不指定容量,則容量默認爲Integer.MAX_VALUE -> 2的31次方-1,此時人爲認定容量是無限的
3. 如果指定容量,則容量指定之後不可變
4. 底層基於節點(鏈表)來存儲數據
5. 遵循先進先出(FIFO)的原則
PriorityBlockingQueue - 阻塞式優先級隊列
1. 在使用的時候可以指定容量也可以不指定
2. 如果不指定容量,則容量默認爲11
3. 如果指定容量,則容量指定之後不可改變
4. 在指定容量的時候,最大不能超過Integer.MAX_VALUE-8
5. PriorityBlockingQueue要求存儲的元素對應的類必須實現Comparable接口,重寫其中的compareTo方法來指定比較規則
6. PriorityBlockingQueue在存儲元素的時候會根據指定的比較規則對元素進行排序
7. PriorityBlockingQueue在迭代的時候不保證元素的排序順序
示例
public class PriorityBlockingQueueDemo {
public static void main(String[] args) throws Exception {
// 創建隊列
PriorityBlockingQueue<Student> queue = new PriorityBlockingQueue<>();
// 添加元素
queue.put(new Student("Amy", 16));
queue.put(new Student("Bob", 25));
queue.put(new Student("Cathy", 20));
queue.put(new Student("David", 13));
// 遍歷隊列
// 需要注意的是,如果想要拿到排序的結果,不能以迭代的方法獲取
for (int i = 0; i < 4; i++) {
System.out.println(queue.take());
}
}
}
class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
// 在這個方法中指定比較規則
// 根據學生的年齡進行升序排序
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
SynchronousQueue - 同步隊列
1. 容量默認爲1,並且只能爲1,因此只能存儲1個元素
2. 如果該隊列已有一個元素,則試圖向隊列中新添一個新元素的線程將會阻塞,直到另一個線程將該元素從隊列中抽走
3. 如果該隊列爲空,則試圖從隊列中抽取一個元素的線程將會阻塞,直到另一個線程向隊列中添加了一個新的元素
4. 一般會將同步隊列稱之爲是數據的匯合點
BlockingDeque - 阻塞式雙向隊列
1. BlockingDeque繼承了BlockingQueue
2. 在使用的時候,需要指定容量的
3. 該隊列稱之爲雙向隊列,即隊列的兩端都可以添加元素,也可以從隊列的兩端獲取元素
4. 因爲雙向,所以添加或獲取時方法需要添加後綴: addFirst:從頭元素添加,addLast:從尾元素添加
ConcurrentMap - 併發映射
- 概述
- 該映射是JDK1.5提供的用於多併發場景下的映射接口
- 該映射的實現類往往是異步式線程安全的 ConcurrentHashMap
ConcurrentHashMap - 併發哈希映射
1. 異步式線程安全的映射
2. 底層是依靠數組+鏈表結構來存儲,數組的默認初始容量是16,默認加載因子是0.75,可指定
3. 採取了分段(桶)鎖機制:當不同線程訪問不同的桶的時候,這個線程會鎖住當前的桶而不是整個映射。在後續版本中,爲了提高併發性,在分段鎖的基礎上,引入了讀寫鎖機制:
a. 讀鎖:允許多個線程讀,不允許寫
b. 寫鎖:只允許一個線程寫,不允許讀
4. 在JDK1.8中,引入了CAS(Compare And Swap , 比較和交換)無鎖算法
==**可參考 作者和大黃-CAS原理分析**==
[https://blog.csdn.net/HEYUTAO007/article/details/19975665](https://blog.csdn.net/HEYUTAO007/article/details/19975665)
5. 從JDK1.8開始,在ConcurrentHashMap/HashMap中引入了紅黑樹機制。如果當桶中的元素超過8個時候,會將這個鏈表扭轉成一棵紅黑樹;如果紅黑樹中的節點個數不足7個,則會將紅黑樹扭轉回鏈表==>紅黑樹扭轉的前提爲桶的個數不少於64,否則擴容
6. 擴展:紅黑樹
紅黑樹參考資料-作者:v_JULY_v-教你初步瞭解紅黑樹
a. 紅黑樹本身是一棵自平衡二叉樹
b. 紅黑樹的查詢時間複雜度:O(logn)
c. 二叉樹的特點:
i. 左子樹和右子樹都是二叉樹
ii. 左子樹小於根節點
iii. 右子樹大於根據點
d. 紅黑樹的特點:
i. 所有的節點非紅即黑
ii. 根節點必須是黑色
iii. 紅節點的子節點必須是黑色,黑節點的子節點可以是紅色也可以是黑色
iv. 所有葉子節點都是黑色的空節點
v. 從根節點到任意一個葉子節點所經歷的黑節點個數是相同的,即黑色節點高度/深度一致
vi. 如果新添子節點,那麼子節點必須是紅色
e. 紅黑樹的修正:
+ 塗色:當前節點爲紅父節點爲紅,並且叔父節點爲紅,將父節點以及叔父節點塗黑,將祖父節點塗紅
+ 左旋:當前節點爲紅父節點爲紅,並且叔父節點爲黑,當前節點是右子葉,以當前節點爲軸進行左旋![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200613130812610.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2MjQ5MTMy,size_16,color_FFFFFF,t_70)
+ 右旋:當前節點爲紅父節點爲紅,並且叔父節點爲黑,當前節點是左子葉,以父節點爲軸進行右旋
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200613130921461.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2MjQ5MTMy,size_16,color_FFFFFF,t_70)
+ 修正案例:
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200613130926440.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2MjQ5MTMy,size_16,color_FFFFFF,t_70)
ConcurrentNavigableMap - 併發導航映射
- 提供了截取自映射的方法,如圖
- 常用的實現類是ConcurrentSkipListMap,該實現類是基於跳躍表實現的
參考資料-作者:Single_YAM-基於跳躍表的 ConcurrentSkipListMap 內部實現
ExecutorService - 執行器服務(線程池)
-
概述
- 本質上是一個線程池
- 線程池定義的目的是爲了減少線程的創建和銷燬以減少內存的消耗和資源的佔用
- 線程池中包含了四個部分:核心線程、臨時線程、工作隊列、拒絕執行助手
- 線程池在剛創建的時候沒有任何的線程,每接收一個新的請求就會創建一個核心線程來處理該請求
- 需要注意的是,在覈心線程創建達到數量之前,即使有核心線程被空出,有新請求過來都會創建一個新的核心線程
- 當所有線程都被佔用之後,再來的請求會被放入工作隊列
- 工作隊列本質上是一個阻塞式隊列
- 當工作隊列被佔用滿之後,後來的請求會被交給臨時線程來處理
- 臨時線程在處理完請求之後會再存活指定的時間,如果在指定時間內有請求過來則利用臨時線程處理;如果沒有請求,則時間倒了之後將臨時線程銷燬
- 當臨時線程也被佔滿,則再來的請求會被交給拒絕執行助手處理
-
示例
public class ExecutorServiceDemo {
@Test
public void test() {
// 創建線程池
// corePoolSize:核心線程數量 5
// maximumPoolSize:最大線程數量,實際上是核心線程+臨時線程 10
// keepAliveTime:臨時線程存活時間 5000
// unit:時間單位 毫秒
// workQueue:工作隊列 阻塞式順序隊列
// handler:拒絕執行助手,該參數可以定義也可以不定義
ExecutorService es = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("拒絕處理線程" + new Thread(r).getName());
}
});
// 執行提交的任務
// 該方法只能執行Runnable線程
es.execute(new EsDemo());
// 關閉線程池
es.shutdown();
}
}
class EsDemo implements Runnable {
@Override
public void run() {
System.out.println("hello~~~");
}
}
- 預定義線程池
java中提供了一個工具類Executors來幫助我們簡單快速創建一個線程池
// 特點:
// 1. 沒有核心線程全部都是臨時線程
// 2. 臨時線程的數量可以認爲是無限的
// 3. 每一個臨時線程都可以存活1min
// 4. 工作隊列爲同步隊列
// 大池子小隊列
// 適用場景:
// 適合於高併發的短任務 - 即時通訊
// 不適合於長任務場景
ExecutorService es = Executors.newCachedThreadPool();
// 特點:
// 1. 全部都是核心線程沒有臨時線程
// 2. 工作隊列是阻塞式鏈式隊列,容量默認是Integer.MAX_VALUE
// 意味着工作隊列足夠大
// 小池子大隊列
// 適用場景:
// 適合於長任務場景 - 下載
// 不適合於短任務場景
ExecutorService es = Executors.newFixedThreadPool(5);
而Executors提供了兩種之星線程的方式
- execute():只能執行Runnable線程
- submit():可以執行Runnable也可以執行Callable線程,且存在返回值,由Future封裝
Callable<?>
-
概述
- JDK1.5提供的一種新的定義線程的方式
- 泛型表示返回值的類型
- 需要重寫call方法,將要執行的邏輯放在該方法中
-
示例
public class CallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 創建線程池
ExecutorService es = Executors.newCachedThreadPool();
// 創建Callable線程
CDemo c = new CDemo();
// 執行Callable線程
// 該方法既可以執行Callable線程也可以執行Runnable線程
// 該方法會將返回值封裝成一個Future對象
Future<String> f = es.submit(c);
// 關閉線程池
es.shutdown();
// 從Future對象中解析結果
System.out.println(f.get());
}
}
class CDemo implements Callable<String> {
@Override
public String call() throws Exception {
return "SUCCESS";
}
}
三、Runnable和Callable的對比
對比項 | Runnable | Callable |
---|---|---|
返回值 | 無,返回值類型爲void | 有,類型用戶指定 |
方法 | run | call |
啓動方式 | 1. 傳入Thread中通過Thread啓動 只能通過線程池啓動 | 2. 通過線程池啓動 |
容錯機制 | 無,出現異常必須在run方法中使用try-catch進行捕獲處理 | 有,call方法允許拋出異常,所以在出現異常之後可以利用全局方式進行異常的處理 |
ScheduledExecutorService
- 概述
- 該接口繼承了ExecutorService
- 該接口能夠將提交的任務延後執行或者間隔固定的時間執行
- 利用該接口可以實現定時執行的效果
- 示例
- schedule 推遲指定的時間再啓動執行線程,這裏推遲了1000毫秒才執行線程
public static void main(String[] args)
{
ScheduledExecutorService ses = Executors.newScheduledThreadPool(5);
System.out.println("run " + System.currentTimeMillis());
// 推遲指定的時間再啓動執行線程
ses.schedule(new Runnable()
{
@Override
public void run()
{
System.out.println("run " + System.currentTimeMillis());
}
}, 1000, TimeUnit.MILLISECONDS);
}
- scheduleAtFixedRate 表示每間隔指定的時間就執行一次
特點
: 從上一次開始時間來計算下一次的啓動時間
: 當線程運行時間大於間隔時間時,按照線程實際運行時間間隔運行
public static void main(String[] args)
{
ScheduledExecutorService ses = Executors.newScheduledThreadPool(5);
System.out.println("run " + System.currentTimeMillis());
// 表示每間隔指定的時間就執行一次
// 從上一次開始時間來計算下一次的啓動時間
ses.scheduleAtFixedRate(new Runnable()
{
@Override
public void run()
{
System.out.println("run " + System.currentTimeMillis());
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}, 0, 3000, TimeUnit.MILLISECONDS);
}
- scheduleWithFixedDelay 表示每間隔指定的時間就執行一次
特點
: 以上一次線程的結束時間開始計算下一次線程的啓動時間(即線程間隔時間爲線程執行時間+設置的間隔時間=兩次線程開始執行的間隔時間)
public static void main(String[] args)
{
ScheduledExecutorService ses = Executors.newScheduledThreadPool(5);
System.out.println("run " + System.currentTimeMillis());
// 表示每間隔指定的時間就執行一次
// 從上一次開始時間來計算下一次的啓動時間
ses.scheduleWithFixedDelay(new Runnable()
{
@Override
public void run()
{
System.out.println("run " + System.currentTimeMillis());
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}, 0, 3000, TimeUnit.MILLISECONDS);
}
ForkJoinPool 分叉合併的線程池
-
概述
- 該線程池是一個用於分叉合併的線程池
- 分叉:將一個大的任務拆分成多個小的任務的過程
- 合併:將所有的小的任務的結果進行彙總的過程
- 分叉合併的目的是爲了提高CPU的利用率
- 爲了避免出現慢任務而導致效率降低,該線程池採取的是work-stealing(工作竊取)策略:即噹噹前CPU核在執行完所有的任務之後不會空閒而是會去掃描其他的CPU核,隨機的從某一個CPU核上“偷”一個任務回來執行
-
示例 計算1-100000000000的和
public class ForkJoinPoolDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 創建分叉合併線程池
ForkJoinPool pool = new ForkJoinPool();
// 提交任務獲取執行結果
Future<Long> f = pool.submit(new Sum(1, 100000000000L));
// 關閉線程池
pool.shutdown();
// 打印結果
System.out.println(f.get());
}
}
// 如果分叉合併完成之後需要計算結果,則繼承RecursiveTask
// 如果分叉合併完成之後需要不計算結果,則繼承RecursiveAction
class Sum extends RecursiveTask<Long> {
private static final long serialVersionUID = -2919639359420237069L;
private long start; // 起始數字
private long end; // 末尾數字
private static final long THRESHOLD = 1000; // 分叉的閾值
public Sum(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
// 判斷數字範圍是否在閾值範圍內
if (end - start <= THRESHOLD) {
long sum = 0;
for (long i = start; i <= end; i++)
sum += i;
return sum;
} else {
long mid = (start + end) / 2;
Sum left = new Sum(start, mid);
Sum right = new Sum(mid + 1, end);
// 分叉
left.fork();
right.fork();
// 合併
return left.join() + right.join();
}
}
}
Lock
-
概述
- Lock是JDK1.5提供的一個表示鎖的新接口
- 提供了用於加鎖的方法lock()以及解鎖的方法unlock()
- 相對傳統的synchronized而言,Lock的方式更加靈活和精細
- 讀寫鎖:接口ReadWriteLock - 實現類ReentrantReadWriteLock
-
公平和非公平策略
- 公平策略:加鎖前檢查是否有排隊等待的線程,優先排隊等待的線程,先來先得
- 非公平策略:加鎖時不考慮排隊等待問題,直接嘗試獲取鎖,獲取不到自動到隊尾等待
- 相對而言,非公平策略的效率更高
- synchronized是非公平策略
- Lock默認是非公平策略,可以手動設置爲公平策略
-
讀寫鎖
- 讀寫鎖分爲讀鎖和寫鎖
- 讀鎖:允許多個線程讀取,但是不允許線程寫入
- 寫鎖:允許一個線程寫入,但是不允許線程讀取
-
其他鎖
- CountDownLatch:閉鎖/線程遞減鎖,可以對線程進行計數。當計數歸零的時候就會放開阻塞 - 當所有的線程到達一個位置之後,開啓另外的任務
package cn.tedu.lock;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(4);
new Thread(new Teacher(cdl)).start();
new Thread(new Student(cdl)).start();
new Thread(new Student(cdl)).start();
new Thread(new Student(cdl)).start();
// 使當前線程陷入阻塞,直到計數歸零纔會放開阻塞
cdl.await();
System.out.println("開始考試~~~");
}
}
class Teacher implements Runnable {
private CountDownLatch cdl;
public Teacher(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("監考老師到達考場");
// 計數減少1個
cdl.countDown();
}
}
class Student implements Runnable {
private CountDownLatch cdl;
public Student(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
System.out.println("考生達到考場");
cdl.countDown();
}
}
當考生和老師全部到達考場的時候纔會進行考試
3. CyclicBarrier:柵欄。也是對線程計數的。當計數歸零的時候就會放開阻塞。所有線程到達同一個點之後再分別繼續往下執行
package cn.tedu.lock;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(4);
new Thread(new Runner(cb), "1號").start();
new Thread(new Runner(cb), "2號").start();
new Thread(new Runner(cb), "3號").start();
new Thread(new Runner(cb), "4號").start();
}
}
class Runner implements Runnable {
private CyclicBarrier cb;
public Runner(CyclicBarrier cb) {
this.cb = cb;
}
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "到了起跑線~~~");
try {
// 讓當前線程陷入阻塞,並且同時減少一個計數
cb.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(name + "跑了出去~~~");
}
}
運動員全部到了起跑線再統一開跑
鎖 | 區別:阻塞位置不同 |
---|---|
CountDownLatch | 當線程全部運行完成後,計數歸零,開啓其他線程執行剩餘任務 |
CyclicBarrier | 當線程到達阻塞點,等待計數歸零後,在繼續線程剩餘的任務 |
5. Exchanger:交換機。用於交換兩個線程之間的信息,泛型表示要交換的信息類型
package cn.tedu.lock;
import java.util.concurrent.Exchanger;
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> ex = new Exchanger<>();
new Thread(new Producer(ex)).start();
new Thread(new Consumer(ex)).start();
}
}
class Producer implements Runnable {
private Exchanger<String> ex;
public Producer(Exchanger<String> ex) {
this.ex = ex;
}
@Override
public void run() {
try {
// 從另一端手中獲取到交換的信息
String info = ex.exchange("商品");
System.out.println("生產者收到了消費者交換的:" + info);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Consumer implements Runnable {
private Exchanger<String> ex;
public Consumer(Exchanger<String> ex) {
this.ex = ex;
}
@Override
public void run() {
try {
String info = ex.exchange("資金");
System.out.println("消費者收到了生產者交換的:" + info);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
6. Semaphore:信號量。表示允許一段邏輯或者對象同時最多有指定個線程可以進入使用,在代碼中用於限流
package cn.tedu.lock;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore s = new Semaphore(3);
for (int i = 0; i < 5; i++) {
new Thread(new Table(s)).start();
}
}
}
class Table implements Runnable {
private Semaphore s;
public Table(Semaphore s) {
this.s = s;
}
@Override
public void run() {
try {
// 獲取到了一個信號,信號量減一
s.acquire();
System.out.println("一張桌子被佔用~~~");
Thread.sleep((long) (10000 * Math.random()));
// 釋放一個信號,信號量加一
s.release();
System.out.println("一張桌子被空出~~~");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
桌子被全部佔用後,只有當桌子空出,纔會重新被佔用