CAS
結合 高併發 文章看!!!! 要不然看不懂
compare and swap. :
比較 和 交換
在多線程 沒有鎖的 情況下 可以保證 多線程對一個 共享變量的 更新
使用:
拿到內存中的最新值,使用CAS嘗試將內存的值修改成目標值 如果修改失敗,
則獲取內存位置的最新值,然後重新嘗試,直到修改成功
注意:
當前值,內存中最新值,目標值
當前值 與 內存中最新值 進行比較 相等 就更新 目標值 不相等 說明 其他線程修改了這個共享變量的值 則然後重新嘗試讀取 當前值 與內存中最新值進行比較,直到修改成功
CAS 缺點:
開銷大 一直do while
它是可以保證原子性的
底層代碼
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
開銷大 那麼怎麼辦呢?
ABA的問題 :
表示 原始共享變量是 0
1號線程 讀取的時候是 0 這個時候
2號線程 讀取0 修改成 2
3號線程 讀取 2 修改成 0
這個時候 1號線程 讀取的 0 實際上 是 變了樣的 0
這就是 aba 問題 。
就好比:
你女朋友 分手之後 找了一個男的 之後 又跟你 複合了
所以 中間發生了什麼 你要感知到 才行 。
解決ABA問題:
共享變量 再 添加一個 版本號 即可
就是 讀取共享變量的時候 不僅讀取值 也要 讀取版本號
那麼 CAS 的底層實現是什麼呢???
cas 和 sychroned 和 volatile 的底層實現 都一樣
JUC
我們使用 多線程的目的 :
就是提高效率
就是 儘可能的 利用 cpu資源 儘可能的利用系統資源
如果 多線程使用 不合理 那麼 可能導致性能 還不如 單線程 導致性能 更低
因爲 多線程消耗的資源更多 涉及到 線程之間的調度 cpu 上下文之間的 切換
以及 線程的 創建及銷燬 還有同步 都是多線程的問題。
jdk 1.5 之前 :
sychornizde 和 volitail
就提供這兩個 用於多線程的
1.5之後 jdk 提供了 java.util.current 包 這裏面提供了 大量 線程相關的 工具類
那麼 多線程 就是 你
如何利用更多的資源 做更多的事情
volatile 關鍵字
1.可見性
2. 有序性
它是不保證 原子性的
package com.juc;
public class VolatileAPP {
public static void main(String[] args) {
/**
*
*/
Data data = new Data();
new Thread(()->{
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
data.flag = true;
System.out.println("flag = " +data.isFlag());
}).start();
while(true){
if(data.isFlag()){
System.out.println("-------------");
break;
}
}
}
}
class Data {
boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
你可以使用 Data implements Runnable 來實現 線程裏面的 run方法
我個人是不習慣 還是喜歡使用 lamda表達式的方式 因爲我 scala 用的比 java熟悉
上面的代碼:
兩個線程:
Thread 用來 修改 flag值
main 用來 反覆while ture 去讀取flag值
那麼 上面的 -----會被打印出來嗎??
以及 while true 能不能結束?
結果:
說明 兩個線程之間 共享數據 是不可見的
因爲 thread 線程 以及 把 flag 修改成 true
而 main 線程 讀取的 flag 卻是 flag
說明是有問題的:
說明 兩個線程之間 共享數據操作 是不可見的
解決:
1. synchronized
這個關鍵字 你加在哪???看高併發的文章!!
1.加在 isFlag方法那塊
2.或者 你加在 while true 裏面的if 都可以
2. volatile
class Data {
boolean flag = false;
public synchronized boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
我修改的是 這
synchronized 效率極低 :
加鎖這種效率是最低的
如果多個線程 來 訪問isFlag 方法 那麼 需要判斷 會發生阻塞! 效率很低
使用 volatile :
他就是 多線程情況下操作 共享數據 是可以保證 共享數據的可見性
即:
class Data {
volatile boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
但是 volatile 是不能保證 原子性的!
原子性:
你可以理解爲 :
一次操作 要麼成功 要麼失敗 所以你一晚上 幾次??
原子性
i++ 的原子性問題!:
很好的一個案例
結合cpu cache : 你可以知道
i++ 操作 實際上是 三步:
1.從主存 讀取 i 放到 cpu cache裏
2. 線程 讀取 cpu cache值 進行修改
3. 修改後的 cpu cache值 寫回 主存
讀 --》修改---》寫回
這三步 每一步都是具有原子性的 但是
三步結合在一起 是不具有原子性的!!!
package com.juc;
public class AtomicApp {
public static void main(String[] args) {
AtomicData atomicData = new AtomicData();
for (int i = 0; i <20 ; i++) {
new Thread(()->{
for (int j = 0; j <1000 ; j++) {
atomicData.increate();
}
},"AtomicApp"+i).start();
}
while (Thread.activeCount() > 2) { //>2 因爲一個 main 一個gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+",number is : " +atomicData.number);
//單線程
// for (int j = 0; j <1000 ; j++) {
// atomicData.increate();
// }
//
// System.out.println(Thread.currentThread().getName()+",number is : " +atomicData.number);
}
}
class AtomicData {
int number = 0;
public int increate(){
return number++;
}
}
結果:
單線程下 結果是 1000 是沒有問題的
多線程情況下 正常結果應該是 20000 但是 不是!!
這就說明 number++; 操作 是不具有原子性 在多線程下 是用問題的!!!
但是真正的理解是:
number ++ 是三步操作
多線程下 你會覺得是 可見性導致的 但是 不準確 因爲 原子性!!
如果是可見性 你加一個volatile 即可 但是 number++ 三步在一起 是不具有原子性的
volatile 是不能解決 原子性的
要想解決:
1。原子變量
jdk1.5之後提供的 java.util.concurrent.atomic包下
2.sychronized
package com.juc;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicApp {
public static void main(String[] args) {
AtomicData atomicData = new AtomicData();
for (int i = 0; i <20 ; i++) {
new Thread(()->{
for (int j = 0; j <1000 ; j++) {
atomicData.increase2();
}
},"AtomicApp"+i).start();
}
while (Thread.activeCount() > 2) { //>2 因爲一個 main 一個gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+",number is : " +atomicData.number);
System.out.println(Thread.currentThread().getName()+",number is : " +atomicData.atomicInteger);
//單線程
// for (int j = 0; j <1000 ; j++) {
// atomicData.increate();
// }
//
// System.out.println(Thread.currentThread().getName()+",number is : " +atomicData.number);
}
}
class AtomicData {
int number = 0;
//第一種解決
public synchronized int increate(){
return number++;
}
//第二種解決
AtomicInteger atomicInteger = new AtomicInteger();
public void increase2(){
atomicInteger.getAndIncrement();
}
}
文章開始提到:
CAS算法是可以解決 原子性問題
那麼原子類 就是調用 CAS算法!! 文章最開始就是源碼
原子變量:
以AtomicInteger 爲例 看源碼
AtomicInteger源碼:
public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value;
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
}
getAndIncrement 實現:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
compareAndSwapInt 實現的就是 cas算法
結論:原子變量
1. volatile int value; 保證值 的 可見性
2. CAS算法保證 數據的原子性
CAS:
當前值,內存中最新值,目標值
可以理解爲:
內存值V
內存中最新值A
更新值B
當且僅當 V==A ,把 B的值 賦值給 V 即 多線程情況下 只有一個線程會成功
cas效率高 是因爲 它不會阻塞 但是如果要自己實現cas的 需要對 爲修改成功後的步驟該
如何做 要自己實現 類似原子類裏面的這個方法!!
下圖 就是cas 過程
模擬CAS算法
package com.juc;
public class CASAPPComplement {
public static void main(String[] args) {
CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i <10 ; i++) {
new Thread(()->{
int expectValue = cas.get();
boolean sucess = cas.compareAndSet(expectValue, (int) (Math.random() * 101));
System.out.println("是否成功:"+sucess);
}).start();
}
}
}
class CompareAndSwap{
int value;
//獲取內存值
public synchronized int get(){
return value;
}
//比較
public synchronized int compareAndSwap(int expectValue, int newValue) {
int oldValue = value;
if (oldValue == expectValue) {
this.value = newValue;
}
return oldValue;
}
//設置值
public synchronized boolean compareAndSet(int expectValue, int newValue){
return expectValue == compareAndSwap(expectValue, newValue);
}
}
底層 絕對不是用 synchronized 這個的哈
ConcurrentHashMap 鎖分段機制
是一個線程安全的 HashMap
它是jdk1.5 之後那個包 裏面提供的
那麼HashMap 和HashTable 有什麼區別??
HashMap 是線程不安全的
HashTable 是線程安全的 效率低!鎖表 實際上就是 並行轉換成 串行的一個操作!!
如果HashTable 遇到 複合操作 就是線程不安全的!:
eg:
若不存在就添加
if(!table.contants()){
table.put
}
contants 和put 方法各自具有 獨立的鎖 但是 和在一起 就不具有獨立的鎖 有線程問題!!
ConcurrentHashMap:鎖分段機制
concurrentLevel : 分段級別 默認的級別是16
也就是ConcurrentHashMap 裏面有16 段 每一段 叫 segment
每個 segment 有獨立的表
好處就是 :
每個 segment 都有一個獨立的鎖
那麼 多線程情況下 就可以併發訪問不同的 segment 效率高
1.8 之後 每隔segment 採用的是 cas 1.5採用的是 鎖
注意:
ConcurrentHashMap -》HashMap
ConcurrentSkipListMap -》TreeMap
CopyOnWriteArrayList -》 ArrayList
CopyOnWriteArraySet -> ArrayAet
這些多線程容器都優於 jdk原生的
那麼他們都解決什麼問題呢??
package com.juc;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* 寫入 並複製
*/
public class CopyOnWriteArrayListApp {
public static void main(String[] args) {
ListTest listTest = new ListTest();
for (int i = 0; i <10 ; i++) {
new Thread(()->{
Iterator<String> it = listTest.list.iterator();
//邊取 邊加
while (it.hasNext()) {
System.out.println(it.next());
listTest.list.add("AA");
}
}).start();
}
}
}
class ListTest{
static List<String> list = Collections.synchronizedList(new ArrayList<String>());
static {
list.add("AA");
list.add("BB");
list.add("CC");
}
}
結果:
發生 併發修改 異常
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.juc.CopyOnWriteArrayListApp.lambda$main$0(CopyOnWriteArrayListApp.java:24)
at java.lang.Thread.run(Thread.java:748)
因爲 讀取 和添加 操作的 是 同一個數據源 就會發生 併發修改異常! 你一個線程跑 也是這個問題
也就是:
不能 讀取 和添加 操作的 是 同一個數據源
使用 CopyOnWriteArrayList 可以解決
class ListTest{
// static List<String> list = Collections.synchronizedList(new ArrayList<String>());
static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
static {
list.add("AA");
list.add("BB");
list.add("CC");
}
}
注意:
但是 添加操作 不適合它 因爲是寫入並複製 複製!!
併發的迭代 操作 它的效率很高!!
CountDownLatch 閉鎖
也就是
當你要完成一些運算 這些運算沒有完成 那就等待這些運算完成
我在繼續執行!!
package com.juc;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchApp {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(10);
long start = System.currentTimeMillis();
//打印 5w 以內偶數
for (int i = 0; i <10 ; i++) {
new Thread(()->{
for (int j = 0; j <50000 ; j++) {
if (j % 2 == 0) {
System.out.println(j);
}
}
}).start();
}
long end = System.currentTimeMillis();
System.out.println("執行時間: " +(end -start));
}
}
上面代碼 想 計算:
10個線程 做 5w以內的偶數打印 的執行時間
是沒有辦法計算的 :
因爲:
main 和 10個線程 是同時執行的 上面的代碼 算得不準確
不能保證 10個線程 執行完了 再計算 執行時間 !!!!
所以有個執行的先後順序的 !!!
解決這個問題:如下
必須用到 閉鎖!!
package com.juc;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchApp {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(10);
long start = System.currentTimeMillis();
//打印 5w 以內偶數
for (int i = 0; i <10 ; i++) {
new Thread(()->{
for (int j = 0; j <50000 ; j++) {
if (j % 2 == 0) {
System.out.println(j);
}
}
latch.countDown();
}).start();
}
latch.await();
long end = System.currentTimeMillis();
System.out.println("執行時間: " +(end -start) +"ms");
}
}
結果:
CountDownLatch latch = new CountDownLatch(10);
10 : 完成一個線程操作 就遞減1 直到減到 0
main 線程 才執行
所以:
1. 10個線程裏 要有 latch.countDown();
2. main 有一個 latch.await()
所以 閉鎖這個操作 應用很廣泛 很重要!!
Lock同步鎖
用於 解決多線程安全的方式:
1. 同步代碼塊 synchronized 可以稱爲隱式鎖
2. 同步方法 synchronized 可以稱爲隱式鎖
jdk1.5 之後 更靈活的方式!!!
3. 同步鎖
是一個顯示鎖 需要 通過lock() 方法上鎖,
必須通過unlock() 方法進行釋放鎖
注意:
必須通過unlock() 方法進行釋放鎖
如果不釋放會有很多問題 最好放在 finally裏面
而 synchronized 就不需要 它是 jvm 底層幫自己維護
package com.juc;
import java.util.concurrent.atomic.AtomicInteger;
public class LockApp {
public static void main(String[] args) {
Ticket ticket = new Ticket();
for (int i = 0; i < 3; i++) {
new Thread(()->{
while (true) {
if (ticket.getTicket() > 0) {
System.out.println(Thread.currentThread().getName()+" 完成售票,餘票爲:"+ --ticket.ticket);
// System.out.println(Thread.currentThread().getName()+" 完成售票,餘票爲:"+ ticket.a.decrementAndGet());
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},i+"號窗口").start();
}
}
}
class Ticket{
int ticket = 100;
// AtomicInteger a = new AtomicInteger(100);
public int getTicket() {
return ticket;
}
}
上面的一個經典案例:
賣票!!
你可以使用 原子類來解決
那麼使用 Lock 該如何做呢??
package com.juc;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockApp {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Lock lock = new ReentrantLock();
for (int i = 0; i < 3; i++) {
new Thread(() -> {
//上鎖
lock.lock();
try {
while (true) {
if (ticket.getTicket() > 0) {
System.out.println(Thread.currentThread().getName() + " 完成售票,餘票爲:" + --ticket.ticket);
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
//釋放鎖
lock.unlock();
}
}, i + "號窗口").start();
}
}
}
class Ticket {
int ticket = 100;
public int getTicket() {
return ticket;
}
}
等待喚醒機制
synchronized:
它是 wait 和 notify 來完成 但是效率低 對吧!!
那麼 如何使用 lock 同步鎖 來實現 等待喚醒機制!!!呢??
package com.juc;
public class ProducerAndConsumerApp {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(()->{
//生產貨物
for (int i = 0; i <20 ; i++) {
producer.clerk.get();
}
},"producer").start();
new Thread(()->{
//賣貨
for (int i = 0; i <20 ; i++) {
consumer.clerk.sale();
}
},"consumer").start();
}
}
//店員
class Clerk{
int product = 0;
//進貨
public synchronized void get(){
if (product >= 10) {
System.out.println("產品已滿!");
}else {
System.out.println(Thread.currentThread().getName()+" : " + ++product);
}
}
//賣貨
public synchronized void sale(){
if (product <= 0) {
System.out.println("缺貨!");
}else {
System.out.println(Thread.currentThread().getName()+" : " + --product);
}
}
}
//producer
class Producer{
Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
}
class Consumer{
Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
}
結果:
producer : 2
producer : 3
producer : 4
producer : 5
producer : 6
producer : 7
producer : 8
consumer : 7
consumer : 6
consumer : 5
consumer : 4
consumer : 3
consumer : 2
consumer : 1
consumer : 0
缺貨!
缺貨!
缺貨!
缺貨!
缺貨!
缺貨!
缺貨!
缺貨!
缺貨!
缺貨!
缺貨!
缺貨!
producer : 1
producer : 2
producer : 3
producer : 4
producer : 5
producer : 6
producer : 7
producer : 8
producer : 9
producer : 10
產品已滿!
產品已滿!
上面是 生產者和消費者案例 :
1.沒有采用 等待喚醒機制 會有什麼問題
生產者 不斷髮送數據 消費值不斷消費 !!
會造成 數據丟失問題!! 或者數據重複消費 問題!!
就看 誰生產快 和消費快了 這是有問題的!!
加入等待喚醒機制!
package com.juc;
public class ProducerAndConsumerApp {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(()->{
//生產貨物
for (int i = 0; i <20 ; i++) {
producer.clerk.get();
}
},"producer").start();
new Thread(()->{
//賣貨
for (int i = 0; i <20 ; i++) {
consumer.clerk.sale();
}
},"consumer").start();
}
}
//店員
class Clerk{
int product = 0;
//進貨
public synchronized void get(){
if (product >= 10) {
System.out.println("產品已滿!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+" : " + ++product);
this.notifyAll();
}
}
//賣貨
public synchronized void sale(){
if (product <= 0) {
System.out.println("缺貨!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+" : " + --product);
this.notifyAll();
}
}
}
//producer
class Producer{
Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
}
class Consumer{
Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
}
結果:
producer : 1
producer : 2
producer : 3
producer : 4
producer : 5
producer : 6
producer : 7
producer : 8
producer : 9
producer : 10
產品已滿!
consumer : 9
consumer : 8
consumer : 7
consumer : 6
consumer : 5
consumer : 4
consumer : 3
consumer : 2
consumer : 1
consumer : 0
缺貨!
producer : 1
producer : 2
producer : 3
producer : 4
producer : 5
producer : 6
producer : 7
producer : 8
producer : 9
consumer : 8
consumer : 7
consumer : 6
consumer : 5
consumer : 4
consumer : 3
consumer : 2
consumer : 1
consumer : 0
只要發現:
生產滿了 就趕緊消費
缺貨 就趕緊生產
這樣纔是 有效的數據!!
這種是效率低的問題嗎???
改一下 :
把生產者 生產貨物 變成1
消費完 再 睡200ms
會造成 程序一隻運行 不會結束!!
package com.juc;
public class ProducerAndConsumerApp {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(()->{
//生產貨物
for (int i = 0; i <20 ; i++) {
producer.clerk.get();
}
},"producer").start();
new Thread(()->{
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//賣貨
for (int i = 0; i <20 ; i++) {
consumer.clerk.sale();
}
},"consumer").start();
}
}
//店員
class Clerk{
int product = 0;
//進貨
public synchronized void get(){
if (product >= 1) {
System.out.println("產品已滿!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+" : " + ++product);
this.notifyAll();
}
}
//賣貨
public synchronized void sale(){
if (product <= 0) {
System.out.println("缺貨!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+" : " + --product);
this.notifyAll();
}
}
}
//producer
class Producer{
Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
}
class Consumer{
Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
}
問題發生在:
sale 和 get方法 裏面的 else那塊
因爲:
當product =0
public synchronized void sale(){
if (product <= 0) {
System.out.println("缺貨!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+" : " + --product);
this.notifyAll();
}
}
代碼運行在 this.wait(); 這
this.wait():
等在這個位置 同時釋放鎖的資源
當他被喚醒之後 從this.wait() 代碼的位置 往下繼續執行
那麼此時 生產者線程:
public synchronized void get(){
if (product >= 1) {
System.out.println("產品已滿!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+" : " + ++product);
this.notifyAll();
}
}
代碼運行 else ++product 導致product 變爲1
然後 this.notifyAll(); 釋放鎖 之後
此時 開始生產者和消費值繼續搶佔cpu 如果 消費者 搶到!!
消費者會 接着從 sale 方法裏面的 this.wait(); 往下執行 跳出else 就是消費者
執行完畢ok了
就剩下 生產者一個線程了
接着 生產者 發現 product=1 會運行
System.out.println("產品已滿!");
this.wait(); 釋放鎖等待被 喚醒 !!! 但是 沒有消費者線程 了
所以不會被喚醒!!
所以代碼會卡住在 : 程序結束不了!!!
產品已滿 或者 缺貨
解決:
把else 去掉
package com.juc;
public class ProducerAndConsumerApp {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(()->{
//生產貨物
for (int i = 0; i <20 ; i++) {
producer.clerk.get();
}
},"producer").start();
new Thread(()->{
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//賣貨
for (int i = 0; i <20 ; i++) {
consumer.clerk.sale();
}
},"consumer").start();
}
}
//店員
class Clerk{
int product = 0;
//進貨
public synchronized void get(){
if (product >= 1) {
System.out.println("產品已滿!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : " + ++product);
this.notifyAll();
}
//賣貨
public synchronized void sale(){
if (product <= 0) {
System.out.println("缺貨!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : " + --product);
this.notifyAll();
}
}
//producer
class Producer{
Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
}
class Consumer{
Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
}
程序可以結束!!
還是有問題的!!:
多個線程 就會有問題
上面是一個生產者一個消費值線程 是沒有問題的
那麼 兩個消費者 兩個生產者 就會有問題!!
package com.juc;
public class ProducerAndConsumerApp {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
for (int j = 0; j < 2; j++) {
new Thread(()->{
//生產貨物
for (int i = 0; i <20 ; i++) {
producer.clerk.get();
}
},"producer"+j).start();
new Thread(()->{
// try {
//// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//賣貨
for (int i = 0; i <20 ; i++) {
consumer.clerk.sale();
}
},"consumer"+j).start();
}
}
}
//店員
class Clerk{
int product = 0;
//進貨
public synchronized void get(){
if (product >= 1) {
System.out.println("產品已滿!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : " + ++product);
this.notifyAll();
}
//賣貨
public synchronized void sale(){
if (product <= 0) {
System.out.println("缺貨!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : " + --product);
this.notifyAll();
}
}
//producer
class Producer{
Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
}
class Consumer{
Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
}
結果:
producer0 : 1
產品已滿!
consumer0 : 0
缺貨!
producer1 : 1
產品已滿!
producer0 : 2
產品已滿!
producer1 : 3
產品已滿!
consumer0 : 2
consumer0 : 1
consumer0 : 0
缺貨!
缺貨!
producer1 : 1
產品已滿!
producer0 : 2
產品已滿!
producer1 : 3
產品已滿!
consumer1 : 2
consumer1 : 1
consumer1 : 0
缺貨!
consumer0 : -1
缺貨!
consumer1 : -2
缺貨!
producer1 : -1
producer1 : 0
producer1 : 1
產品已滿!
producer0 : 2
產品已滿!
producer1 : 3
產品已滿!
consumer1 : 2
consumer1 : 1
consumer1 : 0
缺貨!
consumer0 : -1
缺貨!
consumer1 : -2
缺貨!
producer1 : -1
producer1 : 0
producer1 : 1
產品已滿!
producer0 : 2
產品已滿!
producer1 : 3
產品已滿!
consumer1 : 2
consumer1 : 1
consumer1 : 0
缺貨!
consumer0 : -1
缺貨!
consumer1 : -2
缺貨!
producer1 : -1
producer1 : 0
producer1 : 1
產品已滿!
producer0 : 2
產品已滿!
都出現負數了!!
如何導致的:
public synchronized void sale(){
if (product <= 0) {
System.out.println("缺貨!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : " + --product);
this.notifyAll();
}
兩個線程 都進到這個方法 :
都停在 this.wait(); 如果 同時被 喚醒
product=0 --product之後 就出現負數了
這就是虛假喚醒問題!!!
官網的解釋是 :
this.wait(); 必須使用在 while
所以要把 if 變成 while 爲什麼呢??
就是 要再判斷一次!!! 就解決了!虛假喚醒問題!
package com.juc;
public class ProducerAndConsumerApp {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
for (int j = 0; j < 2; j++) {
new Thread(()->{
//生產貨物
for (int i = 0; i <20 ; i++) {
producer.clerk.get();
}
},"producer"+j).start();
new Thread(()->{
// try {
//// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//賣貨
for (int i = 0; i <20 ; i++) {
consumer.clerk.sale();
}
},"consumer"+j).start();
}
}
}
//店員
class Clerk{
int product = 0;
//進貨
public synchronized void get(){
while (product >= 1) {
System.out.println("產品已滿!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : " + ++product);
this.notifyAll();
}
//賣貨
public synchronized void sale(){
while (product <= 0) {
System.out.println("缺貨!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : " + --product);
this.notifyAll();
}
}
//producer
class Producer{
Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
}
class Consumer{
Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
}
那麼 以上是 使用 synchronized 和 wait + notify的方式 完成等待喚醒機制
如何使用lock完成等待喚醒機制呢???
lock 完成等待喚醒機制
wait 和 notify
Lock 有自己的
叫 Condition 控制線程通信
await、signal、signalAll
Condition 是通過特定的Lock獲取 newCondition
就可以使用等待喚醒機制了!!!
package com.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerAndConsumerLockApp {
public static void main(String[] args) {
Clerk01 clerk = new Clerk01();
Producer01 producer = new Producer01(clerk);
Consumer01 consumer = new Consumer01(clerk);
for (int j = 0; j < 2; j++) {
new Thread(()->{
//生產貨物
for (int i = 0; i <20 ; i++) {
producer.clerk.get();
}
},"producer"+j).start();
new Thread(()->{
//賣貨
for (int i = 0; i <20 ; i++) {
consumer.clerk.sale();
}
},"consumer"+j).start();
}
}
}
//店員
class Clerk01 {
int product = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//進貨
public void get(){
lock.lock();
try{
while (product >= 1) {
System.out.println("產品已滿!");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : " + ++product);
condition.signalAll();
}finally {
lock.unlock();
}
}
//賣貨
public void sale(){
lock.lock();
try{
while (product <= 0) {
System.out.println("缺貨!");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : " + --product);
condition.signalAll();
}finally {
lock.unlock();
}
}
}
//producer
class Producer01 {
Clerk01 clerk;
public Producer01(Clerk01 clerk) {
this.clerk = clerk;
}
}
class Consumer01 {
Clerk01 clerk;
public Consumer01(Clerk01 clerk) {
this.clerk = clerk;
}
}
線程按序交替
eg:
編寫一個程序,開啓3個線程,這三個線程的ID分別是A、B、C、每個線程
將自己的ID在屏幕上打印10遍,要求輸出的結果必須按順序顯示。
如:
ABCABCABC。。。ABC依次遞歸
分析:
1.對線程控制 需要 Lock
2.對線程 按順序控制 就需要 Condition
package com.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ABCAPP {
public static void main(String[] args) {
ABC abc = new ABC();
new Thread(()->{
for (int i = 0; i < 10; i++) {
abc.printA(i);
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
abc.printB(i);
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
abc.printC(i);
System.out.println("---------------------------");
}
},"C").start();
}
}
class ABC{
//當前正在執行線程的標記
int number =1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
/**
*
* @param totalprint 表示循環幾輪
* @throws InterruptedException
*/
public void printA(int totalprint) {
lock.lock();
try{
//1.判斷
if(number !=1){
condition1.await();
}
//2.打印
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+"\t" +i+"\t" +totalprint);
}
//3.喚醒
number =2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(int totalprint) {
lock.lock();
try{
//1.判斷
if(number !=2){
condition2.await();
}
//2.打印
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+"\t" +i+"\t" +totalprint);
}
//3.喚醒
number =3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(int totalprint) {
lock.lock();
try{
//1.判斷
if(number !=3){
condition3.await();
}
//2.打印
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+"\t" +i+"\t" +totalprint);
}
//3.喚醒
number =1;
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
上面的代碼 :
是 10輪 打印10次
結果:
A 0 0
A 1 0
A 2 0
A 3 0
A 4 0
A 5 0
A 6 0
A 7 0
A 8 0
A 9 0
B 0 0
B 1 0
B 2 0
B 3 0
B 4 0
B 5 0
B 6 0
B 7 0
B 8 0
B 9 0
C 0 0
C 1 0
C 2 0
C 3 0
C 4 0
C 5 0
C 6 0
C 7 0
C 8 0
C 9 0
---------------------------
A 0 1
A 1 1
A 2 1
A 3 1
A 4 1
A 5 1
A 6 1
A 7 1
A 8 1
A 9 1
B 0 1
B 1 1
B 2 1
B 3 1
B 4 1
B 5 1
B 6 1
B 7 1
B 8 1
B 9 1
C 0 1
C 1 1
C 2 1
C 3 1
C 4 1
C 5 1
C 6 1
C 7 1
C 8 1
C 9 1
---------------------------
A 0 2
A 1 2
A 2 2
A 3 2
A 4 2
A 5 2
A 6 2
A 7 2
A 8 2
A 9 2
B 0 2
B 1 2
B 2 2
B 3 2
B 4 2
B 5 2
B 6 2
B 7 2
B 8 2
B 9 2
C 0 2
C 1 2
C 2 2
C 3 2
C 4 2
C 5 2
C 6 2
C 7 2
C 8 2
C 9 2
---------------------------
ReadWriteLock讀寫鎖
就是讀寫分離:
什麼情況下使用呢?
1.寫 多線程 就是 寫寫 要互斥
2.讀寫 讀和寫 也要互斥
3.讀讀 是不需要互斥的
package com.juc;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteApp {
public static void main(String[] args) {
/**
* 那麼一個線程去 寫 100 個線程去讀
*/
Demo demo = new Demo();
new Thread(()->{
demo.set((int)(Math.random()*101));
},"write").start();
for (int i = 0; i < 100; i++) {
new Thread(()->{
demo.get();
},"read").start();
}
}
}
class Demo{
int number = 0;
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//讀
public void get(){
readWriteLock.readLock().lock();
try{
System.out.println(Thread.currentThread().getName()+" : " +number);
}finally {
readWriteLock.readLock().unlock();
}
}
//寫
public void set(int number){
readWriteLock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName());
this.number = number;
}finally {
readWriteLock.writeLock().unlock();
}
}
}
結果:
write
read : 36
read : 36
read : 36
read : 36
read : 36
read : 36
。。。
一個線程再 寫 100個線程再 讀 這就是 讀寫鎖
線程八鎖
1.1.兩個普通同步方法 兩個線程 標準打印 , //one two
package com.juc;
/**
* 判斷 打印的是 one 還是 two
*
* 1.兩個普通同步方法 兩個線程 標準打印 , //one two
*
*/
public class Thread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(()->{
number.getOne();
}).start();
new Thread(()->{
number.getTwo();
}).start();
}
}
class Number{
public synchronized void getOne(){
System.out.println("one");
}
public synchronized void getTwo(){
System.out.println("two");
}
}
結果:
one
two
2. 新增 Thread.sleep 給 getone方法
package com.juc;
/**
* 判斷 打印的是 one 還是 two
*
* 1.兩個普通同步方法 兩個線程 標準打印 , //one two
*
*/
public class Thread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(()->{
number.getOne();
}).start();
new Thread(()->{
number.getTwo();
}).start();
}
}
class Number{
public synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo(){
System.out.println("two");
}
}
結果:
one
two
3.新增 getThree 普通方法
package com.juc;
/**
* 判斷 打印的是 one 還是 two
*
* 1.兩個普通同步方法 兩個線程 標準打印 , //one two
*
*/
public class Thread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(()->{
number.getOne();
}).start();
new Thread(()->{
number.getTwo();
}).start();
new Thread(()->{
number.getThree();
}).start();
}
}
class Number{
public synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo(){
System.out.println("two");
}
public void getThree(){
System.out.println("three");
}
}
結果:
three
one
two
4.兩個普通同步方法 兩個Number對象 打印?
package com.juc;
/**
* 判斷 打印的是 one 還是 two
*
* 1.兩個普通同步方法 兩個線程 標準打印 , //one two
*
*/
public class Thread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(()->{
number.getOne();
}).start();
new Thread(()->{
number2.getTwo();
}).start();
// new Thread(()->{
//
// number.getThree();
// }).start();
}
}
class Number{
public synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo(){
System.out.println("two");
}
// public void getThree(){
// System.out.println("three");
// }
}
結果:
two
one
5.修改 getOne爲 靜態的同步方法 打印? two one
package com.juc;
/**
* 判斷 打印的是 one 還是 two
*
* 1.兩個普通同步方法 兩個線程 標準打印 , //one two
*
*/
public class Thread8Monitor {
public static void main(String[] args) {
Number number = new Number();
// Number number2 = new Number();
new Thread(()->{
number.getOne();
}).start();
new Thread(()->{
number.getTwo();
}).start();
// new Thread(()->{
//
// number.getThree();
// }).start();
}
}
class Number{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo(){
System.out.println("two");
}
// public void getThree(){
// System.out.println("three");
// }
}
結果:
two
one
6. 修改兩個同步方法都爲靜態方法
package com.juc;
/**
* 判斷 打印的是 one 還是 two
*
* 1.兩個普通同步方法 兩個線程 標準打印 , //one two
*
*/
public class Thread8Monitor {
public static void main(String[] args) {
Number number = new Number();
// Number number2 = new Number();
new Thread(()->{
number.getOne();
}).start();
new Thread(()->{
number.getTwo();
}).start();
// new Thread(()->{
//
// number.getThree();
// }).start();
}
}
class Number{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public static synchronized void getTwo(){
System.out.println("two");
}
// public void getThree(){
// System.out.println("three");
// }
}
結果:
one
two
7. 一個靜態同步方法 一個非靜態同步方法 兩個Number對象
package com.juc;
/**
* 判斷 打印的是 one 還是 two
*
* 1.兩個普通同步方法 兩個線程 標準打印 , //one two
*
*/
public class Thread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(()->{
number.getOne();
}).start();
new Thread(()->{
number2.getTwo();
}).start();
// new Thread(()->{
//
// number.getThree();
// }).start();
}
}
class Number{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo(){
System.out.println("two");
}
// public void getThree(){
// System.out.println("three");
// }
}
結果:
two
one
8.兩個靜態同步方法 兩個Number對象
package com.juc;
/**
* 判斷 打印的是 one 還是 two
*
* 1.兩個普通同步方法 兩個線程 標準打印 , //one two
*
*/
public class Thread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(()->{
number.getOne();
}).start();
new Thread(()->{
number2.getTwo();
}).start();
// new Thread(()->{
//
// number.getThree();
// }).start();
}
}
class Number{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public static synchronized void getTwo(){
System.out.println("two");
}
// public void getThree(){
// System.out.println("three");
// }
}
結果:
one
two
總結:
線程8鎖 關鍵:
1.非靜態方法的鎖 默認爲 this 靜態方法的鎖 對應的 Class 實例 xxx.class
2.某一個時刻內 只能有一個線程持有鎖 無論幾個方法
線程池
爲什麼使用線程池呢??
之前使用線程 是 new Thread 的方式
這樣是 需要就創建一個線程
實際上就是 創建線程 然後銷燬線程
如果頻繁的創建和銷燬的話,
那麼也是非常耗費資源的。
那麼 你一定使用過數據庫的連接池 。
爲什麼你使用它呢?
性能好呀
爲什麼好呢?
1.不是頻繁的創建和銷燬
2.是去 線程池裏面 取連接就好 用完放回去 (池子裏 先準備好 一些連接)
線程池:1.5之後 線程的使用和調度的 接口 是 EXecutor
1.提供了一個線程隊列 存的是 所有等待狀態的線程
避免創建和銷燬的開銷 提高效率
2.體系結構:
1.Executor : 使用+調度
2.ExecutorService :線程池的主要接口
3.ThreadPoolExecutor : 線程池的實現類
4.ScheduledExecutorSerivce : 線程的調度
5.ScheduledThreadPollExecutor:實現了4 繼承了3
具備線程池和線程的調度的功能
3.工具類:Executors 提供了很多方法!!
eg:
newFixedThreadPoll() 創建固定大小的線程池
newCachedThreadPoll() 線程池的數量不固定 可以根據需求自動更改數量
newSingleThreadExecutor() 創建單個線程池 線程池裏只有一個線程
newScheduledThreadPoll() 創建固定大小的線程 可以延遲定時 的執行任務
package com.juc;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPollApp {
public static void main(String[] args) {
Poll poll = new Poll();
//1.創建線程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
//2.爲線程池中的線程 分配任務
executorService.submit(new Thread(()->{
while (poll.i < 100) {
System.out.println(Thread.currentThread().getName()+" : " + poll.i++);
}
}));
//3.關閉線程池
/**
* shutdown :
* 等待 現有的線程池中的 線程任務完成之後 再關閉 來新的資源 也不接受了
*
* shutdownNow:
* 立即關閉
*/
executorService.shutdown();
}
}
class Poll{
int i =0;
}
結果:
pool-1-thread-1 : 1
pool-1-thread-1 : 2
pool-1-thread-1 : 3
pool-1-thread-1 : 4
pool-1-thread-1 : 5
pool-1-thread-1 : 6
pool-1-thread-1 : 7
pool-1-thread-1 : 8
pool-1-thread-1 : 9
pool-1-thread-1 : 10
pool-1-thread-1 : 11
pool-1-thread-1 : 12
pool-1-thread-1 : 13
pool-1-thread-1 : 14
pool-1-thread-1 : 15
pool-1-thread-1 : 16
pool-1-thread-1 : 17
pool-1-thread-1 : 18
pool-1-thread-1 : 19
。。。
executorService.submit(new Thread(()->{
while (poll.i < 100) {
System.out.println(Thread.currentThread().getName()+" : " + poll.i++);
}
}));
只有一個一個線程 分配任務
package com.juc;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPollApp {
public static void main(String[] args) {
Poll poll = new Poll();
//1.創建線程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
//2.爲線程池中的線程 分配任務
for (int i = 0; i < 10; i++) {
executorService.submit(new Thread(()->{
while (poll.i < 100) {
System.out.println(Thread.currentThread().getName()+" : " + poll.i++);
}
}));
}
//3.關閉線程池
/**
* shutdown :
* 等待 現有的線程池中的 線程任務完成之後 再關閉 來新的資源 也不接受了
*
* shutdownNow:
* 立即關閉
*/
executorService.shutdown();
}
}
class Poll{
int i =0;
}
結果:
pool-1-thread-2 : 1
pool-1-thread-4 : 3
pool-1-thread-4 : 5
pool-1-thread-4 : 6
pool-1-thread-4 : 8
pool-1-thread-4 : 9
pool-1-thread-3 : 2
pool-1-thread-3 : 11
pool-1-thread-3 : 12
pool-1-thread-3 : 13
pool-1-thread-3 : 14
pool-1-thread-3 : 15
pool-1-thread-3 : 16
pool-1-thread-3 : 17
pool-1-thread-3 : 18
pool-1-thread-3 : 19
pool-1-thread-3 : 20
pool-1-thread-3 : 21
pool-1-thread-3 : 22
pool-1-thread-3 : 23
pool-1-thread-3 : 24
pool-1-thread-3 : 25
pool-1-thread-3 : 26
pool-1-thread-3 : 27
pool-1-thread-3 : 28
pool-1-thread-3 : 29
pool-1-thread-1 : 0
pool-1-thread-1 : 31
pool-1-thread-1 : 32
pool-1-thread-1 : 33
pool-1-thread-1 : 34
pool-1-thread-1 : 35
pool-1-thread-1 : 36
pool-1-thread-1 : 37
pool-1-thread-1 : 38
pool-1-thread-1 : 39
pool-1-thread-1 : 40
pool-1-thread-3 : 30
pool-1-thread-3 : 42
pool-1-thread-3 : 43
pool-1-thread-3 : 44
pool-1-thread-3 : 45
pool-1-thread-3 : 46
pool-1-thread-4 : 10
pool-1-thread-4 : 48
pool-1-thread-4 : 49
pool-1-thread-4 : 50
pool-1-thread-5 : 7
pool-1-thread-5 : 52
pool-1-thread-2 : 4
pool-1-thread-2 : 54
pool-1-thread-2 : 55
pool-1-thread-2 : 56
pool-1-thread-2 : 57
pool-1-thread-2 : 58
pool-1-thread-2 : 59
pool-1-thread-2 : 60
pool-1-thread-2 : 61
pool-1-thread-2 : 62
pool-1-thread-2 : 63
pool-1-thread-2 : 64
pool-1-thread-2 : 65
pool-1-thread-2 : 66
pool-1-thread-2 : 67
pool-1-thread-5 : 53
pool-1-thread-5 : 69
pool-1-thread-4 : 51
pool-1-thread-4 : 71
pool-1-thread-4 : 72
pool-1-thread-3 : 47
pool-1-thread-3 : 74
pool-1-thread-3 : 75
pool-1-thread-3 : 76
pool-1-thread-3 : 77
pool-1-thread-3 : 78
pool-1-thread-3 : 79
pool-1-thread-1 : 41
pool-1-thread-1 : 81
pool-1-thread-1 : 82
pool-1-thread-1 : 83
pool-1-thread-1 : 84
pool-1-thread-1 : 85
pool-1-thread-1 : 86
pool-1-thread-1 : 87
pool-1-thread-1 : 88
pool-1-thread-1 : 89
pool-1-thread-1 : 90
pool-1-thread-1 : 91
pool-1-thread-3 : 80
pool-1-thread-4 : 73
pool-1-thread-5 : 70
pool-1-thread-2 : 68
pool-1-thread-5 : 95
pool-1-thread-4 : 94
pool-1-thread-3 : 93
pool-1-thread-1 : 92
pool-1-thread-3 : 99
pool-1-thread-4 : 98
pool-1-thread-5 : 97
pool-1-thread-2 : 96
pool-1-thread-5:
池1: pool-1
線程5: thread-5
明白了嗎?
線程池裏面 5個線程
我分配了 10個任務
callable:
package com.juc;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class ThreadPollApp {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Poll poll = new Poll();
//1.創建線程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<Integer>> list = new ArrayList<>();
//2.爲線程池中的線程 分配任務
for (int j = 0; j < 10; j++) {
Future<Integer> future = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
});
list.add(future);
}
//遍歷10個任務的結果
for (Future<Integer> future: list){
System.out.println(future.get());
}
//2.爲線程池中的線程 分配任務
// for (int i = 0; i < 10; i++) {
//
// executorService.submit(new Thread(()->{
//
// while (poll.i < 100) {
// System.out.println(Thread.currentThread().getName()+" : " + poll.i++);
// }
// }));
// }
//3.關閉線程池
/**
* shutdown :
* 等待 現有的線程池中的 線程任務完成之後 再關閉 來新的資源 也不接受了
*
* shutdownNow:
* 立即關閉
*/
executorService.shutdown();
}
}
class Poll{
int i =0;
}
結果:
4950
4950
4950
4950
4950
4950
4950
4950
4950
4950
線程池+調度
package com.juc;
import java.util.concurrent.*;
public class ScheduledThreadPoolApp {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 10; i++) {
Future<Integer> future = pool.schedule(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num = (int) (Math.random() * 101);
System.out.println(Thread.currentThread().getName() + " : " + num);
return num;
}
}, 1, TimeUnit.SECONDS);
System.out.println(future.get());
}
pool.shutdown();
}
}
結果:
pool-1-thread-1 : 12
12
pool-1-thread-1 : 69
69
pool-1-thread-2 : 39
39
pool-1-thread-1 : 28
28
pool-1-thread-3 : 79
79
pool-1-thread-2 : 17
17
pool-1-thread-4 : 78
78
pool-1-thread-1 : 5
5
pool-1-thread-5 : 62
62
pool-1-thread-3 : 32
32
ForkJoinPool 分支/合併
思想:
1.任務 遞歸分配成若干 小任務(拆到不可再拆)
2.小任務 並行求值
3.結果合併
工作竊取:
之前線程池:
1.4個內核
1號和4號 假如發生線程阻塞
2號和3號 執行完任務
那麼 23是空閒狀態
cpu沒有更加合理的利用
jdk1.7之後 forkjoin:
工作竊取模式 :
把大任務拆分成小任務 ,把小任務 分配到不同的線程中
形成一個線程隊列(裏面是小任務)是雙端隊列 就意味着
一旦某一個線程 獲取任務的時候 獲取不到時候 會發生阻塞 避免阻塞發生
這個線程 會去別的線程 隊列的末尾 偷一個任務進行執行!!
更好的利用cpu資源 效率高 !!
cpu幾個 就有用幾個線程