? ? ? ? 有了多線程,我們就可以讓程序同時做多件事情
作用:
? ? ? ? 提高效率
應用場景:
? ? ? ? 只要想讓多個事情同時運行就需要用到多線程
? ? ? ? 比如:軟件中的耗時操作、所有的聊天軟件、所有的服務器...
并發和并行
? ? ? ?并發:在同一時刻,有多個指令在單個CPU上交替執行
? ? ? ? 并行:在同一時刻,有多個指令在多個CPU上同時執行
多線程的實現方式:
①繼承Thread類的方式進行實現
實現步驟:
????????1.自己定義一個類繼承Thread
????????2.重寫run方法
????????3.創建子類對象,并啟動線程
代碼演示:
? ? ? ? MyThread類(繼承Thread):
public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() + "aaa");}}
}
? ? ? ? 測試類ThreadDemo1:
public class ThreadDemo1 {public static void main(String[] args) {/*1.創建類繼承Thread2.重寫run方法3.創建子類對象,啟動線程*///創建子類對象MyThread t1 = new MyThread();MyThread t2 = new MyThread();//給線程起名字便于觀察結果t1.setName("線程1");t2.setName("線程2");//啟動線程t1.start();t2.start();}
}
? ? ? ? 運行結果:
(只截取了一部分)兩個進程并發執行
????????
②實現Runnable接口的方式進行實現
?實現步驟:
????????1.定義一個類implements Runnable接口
????????2.重寫run方法
????????3.創建這個類的對象
????????4.創建線程對象,啟動線程
代碼演示:
? ? ? ? MyRun類(實現Runnable):
public class MyRun implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "aaa");}}
}
? ? ? ? 測試類ThreadDemo2:
public class ThreadDemo2 {public static void main(String[] args) {/*1.定義一個類implements Runnable接口2.重寫run方法3.創建這個類的對象4.創建線程對象,啟動線程*///創建這個類的對象MyRun mr = new MyRun();//創建線程對象Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);//起名字t1.setName("線程1");t2.setName("線程2");//啟動線程t1.start();t2.start();}
}
? ? ? ? 運行結果:
(只截取了一部分)兩個進程并發執行
????????
★③利用Callable接口和Future接口方式實現
代碼演示:
?? ? ? ? MyCallable類(實現Callable):
public class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i < 100; i++) {sum = sum + i;}return sum;}
}
? ? ? ? 測試類ThreadDemo3:
public class ThreadDemo3 {public static void main(String[] args) throws ExecutionException, InterruptedException {/*1.定義一個類MyCallable實現Callable接口2.重寫call方法(有返回值,表示多線程運行的結果)3.創建MyCallable的對象(表示多線程要執行的任務)4.創建FutureTask的對象(作用是管理多線程運行的結果)5.創建Thread類的對象,啟動線程*///創建MyCallable的對象MyCallable mc = new MyCallable();//創建FutureTask的對象FutureTask<Integer> ft = new FutureTask<>(mc);//創建Thread類的對象Thread t1 = new Thread(ft);//啟動線程t1.start();//獲取結果Integer result = ft.get();System.out.println(result);}
}
? ? ? ? 運行結果:
????????
多線程三種實現方式對比
常見成員方法:
????????其中前四種方法比較簡單,在此簡單介紹幾點
? ? ? ? 1.線程的默認名字是Thread-序號,序號從0開始,隨著進程創建按順序逐個+1
? ? ? ? 2.第三個第四個方法都是哪個線程實現這兩個成員方法所在的方法,則是對這個線程操作
? ? ? ? 3.讓線程休眠后續代碼也會運行
優先級:
? ? ? ? 線程的優先級從1~10分為10擋,1為優先級最低,10為最高。
? ? ? ? 優先級越高,在線程并發中搶到CPU的概率就更高(但不是絕對的)。
? ? ? ? 線程的默認優先級都為5。
代碼演示:
MyThread類:
public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() + ":" + i);}}
}
測試類ThreadDemo4:
public class ThreadDemo4 {public static void main(String[] args) {//創建進程MyThread t1 = new MyThread();MyThread t2 = new MyThread();//設置優先級t1.setPriority(10);t2.setPriority(1);//設置名字t1.setName("線程1");t2.setName("線程2");t1.start();t2.start();}
}
運行結果:
守護線程:
? ? ? ? 特點:
? ? ? ? ? ? ? ? 當其他非守護線程執行完畢之后,守護線程也會陸續結束,不管是否執行完代碼。
代碼演示:
MyThread1:
public class MyThread1 extends Thread {@Overridepublic void run() {for (int i = 0; i <= 100; i++) {System.out.println(getName() + ":" + i);}}
}
MyThread2:
public class MyThread2 extends Thread {@Overridepublic void run() {for (int i = 0; i <= 10; i++) {System.out.println(getName() + ":" + i);}}
}
測試類ThreadDemo5:
public class ThreadDemo5 {public static void main(String[] args) {//創建進程MyThread1 t1 = new MyThread1();MyThread2 t2 = new MyThread2();//設置名字t1.setName("沸羊羊");t2.setName("美羊羊");t1.setDaemon(true);t1.start();t2.start();}
}
運行結果:
出讓線程:
? ? ? ? 執行Thread.yield命令后就是把CPU的執行權交出,然后各個線程重新搶奪。
插隊線程:
? ? ? ? 執行方法 ‘線程對象.join()’ 后 表示把這個線程,插入到當前線程之前,先執行完這個線程,再執行當前方法的線程。
線程的生命周期
線程的生命周期是:新建狀態,就緒狀態,運行狀態,阻塞狀態,死亡狀態
虛擬機中線程的六種狀態
新建狀態、就緒狀態、阻塞狀態、等待狀態、計時等待、結束狀態 (沒有運行狀態)
多線程代碼編寫核心邏輯
? ? ? ? 1.循環
? ? ? ? 2.同步代碼塊
????????3.判斷共享數據是否到了末尾(到了末尾)
? ? ? ? 4.判斷共享數據是否到了末尾(沒到末尾,執行核心邏輯)
同步代碼塊:
格式:
? ? ? ? synchronized (鎖對象) {
? ? ? ? ? ? ? ? 同步代碼塊
????????}
特點:
? ? ? ? 當一個線程搶到CPU的執行權進入到同步代碼塊中執行代碼了,那么其他的線程是不能再進來同步代碼塊的,只有當成功進入的線程執行完畢出去之后,它的鎖才打開,其他的線程才能進來,而且也只能進去一個線程。
? ? ? ? 并且,如果鎖對象不唯一,那么相當于有好幾把鎖,就可能出現不同的線程看的是不同的鎖,還是會同時進去執行代碼。一般我們用當前類的字節碼文件作為鎖對象(類名.class).
? ? ? ? 先結合一個小練習進行代碼演示
練習:
? ? ? ? 某電影院目前正在上映國產大片,共有1000張票,而它有3個賣票窗口,請設計一個程序模擬該電影院買票。
代碼演示:
MyThread3類:
public class MyThread3 extends Thread {//表示這個類所有的對象,都共享ticket數據static int ticket = 0;//鎖對象,必須是唯一的@Overridepublic void run() {while (true) {//同步代碼塊synchronized (MyThread3.class) {if(ticket < 1000) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(Thread.currentThread().getName() + "正在賣第" + ticket + "張票");} else {break;}}}}
}
測試類ThreadDemo6:
public class ThreadDemo6 {public static void main(String[] args) {//某電影院目前正在上映國產大片,共有1000張票,而它有3個賣票窗口,請設計一個程序模擬該電影院買票。//創建三個線程對象MyThread3 t1 = new MyThread3();MyThread3 t2 = new MyThread3();MyThread3 t3 = new MyThread3();//設置名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//啟動線程t1.start();t2.start();t3.start();}
}
運行結果:
同步方法:
? ? ? ? 同步方法就是把synchronized關鍵字加到方法上
格式:
? ? ? ? 修飾符 synchronized 返回值類型 方法名(方法參數) {...}
特點:
? ? ? ? 1.同步方法是鎖住方法里面所有的代碼
? ? ? ? 2.鎖對象不能自己指定(非靜態方法中是this,靜態方法中是當前類的字節碼文件對象)
? ? ? ? 繼續結合上述小練習進行代碼演示
練習:
? ? ? ? 某電影院目前正在上映國產大片,共有1000張票,而它有3個賣票窗口,請設計一個程序模擬該電影院買票。
代碼演示:
MyRunnable類:
public class MyRunnable implements Runnable {int ticket = 0;@Overridepublic void run() {while(true) {if (method()) break;}}//同步方法private synchronized boolean method() {if(ticket == 1000) {return true;} else {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(Thread.currentThread().getName() + "正在賣第" + ticket + "張票");}return false;}
}
測試類ThreadDemo7:
public class ThreadDemo7 {public static void main(String[] args) {//某電影院目前正在上映國產大片,共有1000張票,而它有3個賣票窗口,請設計一個程序模擬該電影院買票。MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);Thread t3 = new Thread(mr);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
運行結果:
lock鎖:
????????繼續結合上述小練習進行代碼演示
練習:
? ? ? ? 某電影院目前正在上映國產大片,共有1000張票,而它有3個賣票窗口,請設計一個程序模擬該電影院買票。
代碼演示:
MyThread類:
public class MyThread extends Thread {static int ticket = 0;//創建鎖對象static Lock lock = new ReentrantLock();@Overridepublic void run() {while(true) {lock.lock();try {if(ticket == 1000) {break;} else {Thread.sleep(10);ticket++;System.out.println(Thread.currentThread().getName() + "正在賣第" + ticket + "張票");}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}}
}
測試類ThreadDemo8:
public class ThreadDemo8 {public static void main(String[] args) {//某電影院目前正在上映國產大片,共有1000張票,而它有3個賣票窗口,請設計一個程序模擬該電影院買票。//創建線程MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();//設置名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//啟動線程t1.start();t2.start();t3.start();}
}
運行結果:
死鎖:
概念:
? ? ? ? 死鎖是多線程中的一種錯誤
????????舉個例子:
????????兩個人在一起吃飯,桌上只有一雙筷子,有三個條件
????????????????①每次需要拿起筷子才能吃飯
????????????????②一次只能拿一只筷子
????????????????③拿到一雙筷子后可以吃一口
? ? ? ? 在一個人拿到一只筷子,另一個人也拿到一只筷子時,這時候就發生了死鎖,程序無法結束
注意:
? ? ? ? 關于死鎖需要注意,在設計程序時盡量避免發生鎖的嵌套
生產者和消費者(等待喚醒機制)
1.基本寫法:
? ? ? ? 假如現在有一個廚師和一個食客,食客吃10份食物就飽了,當桌子上有食物時,食客就會吃一份,吃完通知廚師再做一份,如果沒有食物,就等待。廚師在桌子上沒有食物時,就再做一份,如果有食物則等待。
代碼演示:
桌子Desk類:
public class Desk {//定義變量表示桌子上是否有食物 0:沒有 1:有public static int foodFlag = 0;//定義變量存儲食客還能吃幾碗 食客最多能吃10碗 初始值為10public static int count = 10;//鎖對象public static Object lock = new Object();
}
食客Foodie類:
public class Foodie extends Thread {@Overridepublic void run() {while (true) {synchronized (Desk.lock) {//判斷還能吃嗎if(Desk.count == 0) {//不能吃了break;} else {//還能吃//判斷桌子上有沒有食物if(Desk.foodFlag == 0) {//如果沒有食物//等待(調用鎖對象的wait方法讓食客線程阻塞)try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}} else {//如果有食物Desk.count--;if(Desk.count == 0) {System.out.println("食客吃完了一份食物,吃飽了");} else {System.out.println("食客吃完了一份食物,還能吃" + Desk.count + "份");}//將桌子上置為沒有食物Desk.foodFlag = 0;//通知廚師Desk.lock.notifyAll();//喚醒這個鎖對象內的所有線程}}}}}
}
廚師Cooker類:
public class Cooker extends Thread {@Overridepublic void run() {while (true) {synchronized (Desk.lock) {//判斷食客是否吃飽了if(Desk.count == 0) {//吃飽了break;} else {//沒吃飽//判斷桌子上是否有食物if(Desk.foodFlag == 1) {//有食物try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}} else {//沒有食物System.out.println("廚師制作好了食物放到了桌子上");//將桌子置為有食物Desk.foodFlag = 1;//喚醒食客Desk.lock.notifyAll();//喚醒這個鎖對象內的所有線程}}}}}
}
測試類Test:
public class Test {public static void main(String[] args) {//創建線程對象Foodie foodieThread = new Foodie();Cooker cookerThread = new Cooker();//啟動線程foodieThread.start();cookerThread.start();}
}
運行結果:
2.利用阻塞隊列:
????????假如現在有一個廚師和一個食客,食客吃10份食物就飽了,廚師可以不斷的做好食物并放到窗口上,窗口上最多可以放3碗。當窗口上有食物時,食客就會吃,如果沒有食物,就等待。
代碼演示:
食客Foodie類:
public class Foodie extends Thread {ArrayBlockingQueue<String> queue;public Foodie(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while (true) {try {String food = queue.take();System.out.println("食客吃了一份" + food);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
廚師Cooker類:
public class Cooker extends Thread {ArrayBlockingQueue<String> queue;public Cooker(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while (true) {try {queue.put("面條");System.out.println("廚師放了一碗面條");} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
測試類Test:
public class Test {public static void main(String[] args) {/*假如現在有一個廚師和一個食客,廚師可以不斷的做好食物并放到窗口上,窗口上最多可以放3碗。當窗口上有食物時,食客就會吃,如果沒有食物,就等待。*/ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);Foodie foodieThread = new Foodie(queue);Cooker cookerThread = new Cooker(queue);foodieThread.start();cookerThread.start();}
}
運行結果:
(因為輸出語句在鎖的外面,所以輸出結果不一定和真是數據傳輸一致)
練習題
練習一:
????????一共有1000張電影票,可以在兩個窗口領取,假設每次領取的時間為3000毫秒,
????????要求:用多線程模擬賣票過程并打印剩余電影票的數量
代碼演示:
MyThread類:
public class MyThread extends Thread {//定義還剩多少票static int ticket = 1000;@Overridepublic void run() {while(true) {synchronized (MyThread.class) {//判斷是否還有票if(ticket == 0) {//沒票了break;} else {//有票//每次領取時間3000毫秒try {sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket--;if(ticket != 0) {System.out.println(getName() + "賣了一張票,還剩" + ticket + "張票");} else {System.out.println(getName() + "賣了一張票,票賣完了");}}}}}
}
測試類Test:
public class Test {public static void main(String[] args) {/*一共有1000張電影票,可以在兩個窗口領取,假設每次領取的時間為3000毫秒,要求:用多線程模擬賣票過程并打印剩余電影票的數量*///創建線程對象MyThread t1 = new MyThread();MyThread t2 = new MyThread();//設置名字t1.setName("窗口1");t2.setName("窗口2");//啟動線程t1.start();t2.start();}
}
運行結果:
?練習二:
????????有100份禮品,兩人同時發送,當剩下的禮品小于10份的時候則不再發出
????????利用多線程模擬該過程并將線程的名字和禮物的剩余數量打印出來
代碼演示:
MyThread類:
public class MyThread extends Thread {//剩余禮物數量static int gift = 100;@Overridepublic void run() {while (true) {synchronized (MyThread.class) {//判斷禮物數量if(gift == 9) {//禮物數量小于10break;} else {//禮物數量不小于10gift--;System.out.println(getName() + "分發了一份禮物,還剩" + gift + "份");}}}}
}
測試類Test:
public class Test {public static void main(String[] args) {/*有100份禮品,兩人同時發送,當剩下的禮品小于10份的時候則不再發出利用多線程模擬該過程并將線程的名字和禮物的剩余數量打印出來*///創建線程對象MyThread t1 = new MyThread();MyThread t2 = new MyThread();//設置名字t1.setName("分發員1");t2.setName("分發員2");//啟動線程t1.start();t2.start();}
}
運行結果:
?練習三:
????????同時開啟了兩個線程,共同獲取1~100之間的所有數字
????????要求:輸出所有的奇數
代碼演示:
MyThread類:
public class MyThread extends Thread {static int num = 1;@Overridepublic void run() {while (true) {synchronized (MyThread.class) {if(num > 100) {break;} else {if(num % 2 == 1) {System.out.println(getName() + ":" + num);}num++;}}}}
}
測試類Test:
public class Test {public static void main(String[] args) {/*同時開啟了兩個線程,共同獲取1~100之間的所有數字要求:輸出所有的奇數*/MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.start();t2.start();}
}
運行結果:
?練習四:
????????搶紅包也用到了多線程
????????假設:100塊,分成了3個包,現在有五個人去搶
????????其中,紅包是共享數據 5個人是5條線程
????????打印結果如下:
????????????????XXX搶到了XXX元
????????????????XXX搶到了XXX元
????????????????XXX搶到了XXX元
????????????????XXX沒搶到
????????????????XXX沒搶到
代碼演示:
MyThread類:
public class MyThread extends Thread {//紅包個數static int count = 3;//紅包中剩余金額static BigDecimal money = BigDecimal.valueOf(100);//紅包金額最小值static final BigDecimal MIN = BigDecimal.valueOf(0.01);@Overridepublic void run() {//一人搶一次所以不用循環synchronized (MyThread.class) {//對紅包數量判斷if(count == 0) {//搶完了System.out.println(getName() + "沒搶到紅包");} else {//沒搶完BigDecimal prize;if(count == 1) {//只剩一個紅包prize = money;} else {//還剩多個紅包Random r = new Random();double bounds = money.subtract(MIN.multiply(BigDecimal.valueOf(count - 1))).doubleValue();prize = BigDecimal.valueOf(r.nextDouble(bounds));if (prize.compareTo(MIN) == -1) {//prize小于MINprize = MIN;}}//設置保留兩位小數,四舍五入prize = prize.setScale(2, RoundingMode.HALF_UP);System.out.println(getName() + "搶到了" + prize + "元");//從紅包金額中減去搶到的金額money = money.subtract(prize);//紅包個數減一count--;}}}
}
測試類Test:
public class Test {public static void main(String[] args) {/*搶紅包也用到了多線程假設:100塊,分成了3個包,現在有五個人去搶其中,紅包是共享數據5個人是5條線程打印結果如下:XXX搶到了XXX元XXX搶到了XXX元XXX搶到了XXX元XXX沒搶到XXX沒搶到*/MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();MyThread t4 = new MyThread();MyThread t5 = new MyThread();t1.setName("小A");t2.setName("小B");t3.setName("小C");t4.setName("小D");t5.setName("小E");t1.start();t2.start();t3.start();t4.start();t5.start();}
}
運行結果:
???????
?練習五:
????????有一個抽獎池,該抽獎池中存放了獎勵的金額,
????????該抽獎池中的獎項為: {10,5,20,50,100,200,500,800,2,80,300,700}
????????創建兩個抽獎箱(線程) 隨機從抽獎池中獲取獎項元素并打印在控制臺上
????????格式如下:
????????????????抽獎箱1產生了一個10元大獎
????????????????抽獎箱2產生了一個100元大獎
????????????????抽獎箱2產生了一個800元大獎
????????????????抽獎箱1產生了一個200元大獎
????????????????...
代碼演示:
MyThread類:
public class MyThread extends Thread {ArrayList<Integer> list;public MyThread(ArrayList<Integer> list) {this.list = list;}@Overridepublic void run() {while((true)) {synchronized (MyThread.class) {if(list.isEmpty()) {//集合為空break;} else {//集合不為空Collections.shuffle(list);int prize = list.remove(0);System.out.println(getName() + "產生了一個" + prize + "元大獎");}}//鎖外睡10毫秒,避免結果只出現一個線程try {sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
測試類Test:
public class Test {public static void main(String[] args) {/*有一個抽獎池,該抽獎池中存放了獎勵的金額,該抽獎池中的獎項為:{10,5,20,50,100,200,500,800,2,80,300,700}創建兩個抽獎箱(線程)隨機從抽獎池中獲取獎項元素并打印在控制臺上格式如下:抽獎箱1產生了一個10元大獎抽獎箱2產生了一個100元大獎抽獎箱2產生了一個800元大獎抽獎箱1產生了一個200元大獎...*///創建抽獎池ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);MyThread t1 = new MyThread(list);MyThread t2 = new MyThread(list);t1.setName("抽獎箱1");t2.setName("抽獎箱2");t1.start();t2.start();}
}
運行結果:
????????????????????????????
?練習五Pro1:
????????在上一題(練習五)的基礎上繼續完成如下需求:
???????? 每次抽的過程中,不打印,抽完時一次性打印(隨機)
????????格式如下:
????????????????在此次抽獎過程中,抽獎箱1總共產生了6個獎項
????????????????分別為:10,20,100,500,2,300最高獎項為300元,總計額為932元
????????????????在此次抽獎過程中,抽獎箱2總共產生了6個獎項
????????????????分別為:5,50,200,800,80,700最高獎項為800元,總計額為1835元
代碼演示:
MyThread類:
public class MyThread extends Thread {ArrayList<Integer> list;int max = 0;int sum = 0;public MyThread(ArrayList<Integer> list) {this.list = list;}public String boxToString(ArrayList<Integer> box) {StringBuffer sb = new StringBuffer();for (int i = 0; i < box.size(); i++) {if (i != box.size() - 1) {sb.append(box.get(i) + ",");} else {sb.append(box.get(i));}}return sb.toString();}@Overridepublic void run() {ArrayList<Integer> box = new ArrayList<>();while ((true)) {synchronized (com.han.thread.test5.MyThread.class) {if (list.isEmpty()) {//集合為空System.out.println("在此次抽獎過程中," + getName() + "總共產生了" + box.size() + "個獎項\n" +"分別為:" + boxToString(box) + "最高獎項為" + max + "元,總計額為" + sum + "元");break;} else {//集合不為空Collections.shuffle(list);int prize = list.remove(0);box.add(prize);sum = sum + prize;if (prize > max) {max = prize;}//System.out.println(getName() + "產生了一個" + prize + "元大獎");}}//鎖外睡10毫秒,避免結果只出現一個線程try {sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
測試類Test:
public class Test {public static void main(String[] args) {/*在上一題(練習五)的基礎上繼續完成如下需求:每次抽的過程中,不打印,抽完時一次性打印(隨機)格式如下:在此次抽獎過程中,抽獎箱1總共產生了6個獎項分別為:10,20,100,500,2,300最高獎項為300元,總計額為932元在此次抽獎過程中,抽獎箱2總共產生了6個獎項分別為:5,50,200,800,80,700最高獎項為800元,總計額為1835元*///創建抽獎池ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);MyThread t1 = new MyThread(list);MyThread t2 = new MyThread(list);t1.setName("抽獎箱1");t2.setName("抽獎箱2");t1.start();t2.start();}
}
運行結果:
???????????????????????????????????
?練習五Pro2:
????????在上一題基礎上繼續完成如下需求:
???????? 每次抽的過程中,不打印,抽完時一次性打印(隨機)
????????格式如下:
????????????????在此次抽獎過程中,抽獎箱1總共產生了6個獎項
????????????????分別為:10,20,100,500,2,300最高獎項為300元,總計額為932元
????????????????在此次抽獎過程中,抽獎箱2總共產生了6個獎項
????????????????分別為:5,50,200,800,80,700最高獎項為800元,總計額為1835元
????????????????在此次抽獎過程中,抽獎箱2中產生了最大獎項,該獎項金額為800元
代碼演示:
MyCallable類:
public class MyCallable implements Callable<Integer> {ArrayList<Integer> list;public MyCallable(ArrayList<Integer> list) {this.list = list;}public String boxToString(ArrayList<Integer> box) {StringBuffer sb = new StringBuffer();for (int i = 0; i < box.size(); i++) {if (i != box.size() - 1) {sb.append(box.get(i) + ",");} else {sb.append(box.get(i));}}return sb.toString();}@Overridepublic Integer call() throws Exception {int max = 0;int sum = 0;ArrayList<Integer> box = new ArrayList<>();while ((true)) {synchronized (com.han.thread.test5.MyThread.class) {if (list.isEmpty()) {//集合為空System.out.println("在此次抽獎過程中," + Thread.currentThread().getName() + "總共產生了" + box.size() + "個獎項\n" +"分別為:" + boxToString(box) + "最高獎項為" + max + "元,總計額為" + sum + "元");break;} else {//集合不為空Collections.shuffle(list);int prize = list.remove(0);box.add(prize);sum = sum + prize;if (prize > max) {max = prize;}//System.out.println(getName() + "產生了一個" + prize + "元大獎");}}//鎖外睡10毫秒,避免結果只出現一個線程Thread.sleep(10);}if(box.isEmpty()) {return null;} else {return Collections.max(box);}}}
測試類Test:
public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {/*在上一題基礎上繼續完成如下需求:每次抽的過程中,不打印,抽完時一次性打印(隨機)格式如下:在此次抽獎過程中,抽獎箱1總共產生了6個獎項分別為:10,20,100,500,2,300最高獎項為300元,總計額為932元在此次抽獎過程中,抽獎箱2總共產生了6個獎項分別為:5,50,200,800,80,700最高獎項為800元,總計額為1835元在此次抽獎過程中,抽獎箱2中產生了最大獎項,該獎項金額為800元*///創建抽獎池ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);//創建多線程要運行的參數對象MyCallable mc = new MyCallable(list);//創建多線程運行結果的管理者對象FutureTask<Integer> ft1 = new FutureTask<>(mc);FutureTask<Integer> ft2 = new FutureTask<>(mc);//創建線程對象Thread t1 = new Thread(ft1);Thread t2 = new Thread(ft2);//設置名字t1.setName("抽獎箱1");t2.setName("抽獎箱2");//啟動t1.start();t2.start();//輸出結果int max1 = ft1.get();int max2 = ft2.get();if (max1 > max2) {System.out.println("在此次抽獎過程中,抽獎箱1中產生了最大獎項,該獎項金額為" + max1 + "元");} else {System.out.println("在此次抽獎過程中,抽獎箱2中產生了最大獎項,該獎項金額為" + max2 + "元");}}
}
運行結果:
???????????????????????????????????
線程池
核心原理:
? ? ? ? ①創建一個池子,池子中是空的
? ? ? ? ②提交任務時,池子會創建新的線程對象,任務執行完畢,線程歸還給池子,下回再次提交任務時,不需要創建新的線程,直接復用已有的線程即可
? ? ? ? ③但是如果提交任務時,池子中沒有空閑的線程,也無法創建新的線程,任務就會排隊等待
創建方法:
代碼演示:
MyRunnable類:
public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 1; i <= 1000; i++) {System.out.println(Thread.currentThread().getName() + "-" + i);}}
}
測試類ThreadPoolDemo1:
public class ThreadPoolDemo1 {public static void main(String[] args) throws InterruptedException {//創建一個沒有上限的線程池(上限二十一億多,但是創建不了這么多機器就承受不住)ExecutorService pool1 = Executors.newCachedThreadPool();System.out.println("無上限線程池");pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());//銷毀線程池pool1.shutdown();//創建一個有上線的線程池ExecutorService pool2 = Executors.newFixedThreadPool(3);System.out.println("有上限線程池");pool2.submit(new MyRunnable());pool2.submit(new MyRunnable());pool2.submit(new MyRunnable());pool2.submit(new MyRunnable());pool2.submit(new MyRunnable());//銷毀線程池pool2.shutdown();}
}
自定義線程池
核心原理:
????????①創建一個池子,池子中是空的
? ? ? ? ②有任務提交時,線程池會創建線程去執行任務,執行完畢歸還線程
不斷的提交任務,會有以下三個臨界點
? ? ? ? ①當核心線程滿時,再提交任務就會排隊
? ? ? ? ②當核心線程滿,隊伍滿時,會創建臨時線程
? ? ? ? ③當核心線程滿,隊伍滿,臨時線程滿時,會觸發任務拒絕策略
任務拒絕策略:
創建代碼演示:
public class ThreadPoolDemo2 {public static void main(String[] args) {/*ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心線程數量,最大線程數量,空閑線程最大存活時間,時間單位,任務隊列,創建線程工廠,任務的拒絕策略)參數一:核心線程數量 不能小于0參數二:最大線程數量 不能小于0,最大數量>=核心線程數量參數三:空閑線程最大存活時間 不能小于0參數四:時間單位 用TimeUnit指定參數五:任務隊列 不能為null參數六:創建線程工廠 不能為null參數七:任務的拒絕策略 不能為null*/ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,6,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());}
}
線程池多大合適
前置概念:
最大并行數:
? ? ? ? 如果電腦是四核八線程,那么最大并行數就是8
? ? ? ? 我的電腦是八核十六線程,所以最大并行數是16
1.CPU密集型運算:
? ? ? ? 計算比較多,讀取本地文件或者數據庫的操作比較少
線程池大小:
? ? ? ? 這種情況線程池大小最好為最大并行數+1=17,多出來那一個用來候補,,在遇到問題時頂上去
2.I/O密集型運算:
????????讀取本地文件或者數據庫的操作比較多
線程池大小:
? ? ? ? 有下面公式求出
???????
????????這里期望CPU利用率我們設為100%,假如要從本地文件中讀取兩個數據并進行相加,獲取數據由硬盤完成,不計算在CPU計算時間中,假設讀取數據用1秒,CPU計算用1秒,那么總時間就為2秒,線程池大小就等于16(最大并行數)* 100% * 2/1 = 32
? ? ? ? CPU計算時間可以通過thread dump工具類獲取,然后計算