多執行緒 學習筆記(2021.10.13~17)
多執行緒
目錄一、執行緒簡介
任務、程序、執行緒、多執行緒
1. 多工
同時進行兩個任務:邊玩遊戲邊聽歌
2. 多執行緒
一條路只能走一輛車,想要一條路走兩輛車需要分出很多道,這樣太能同時開兩輛車(類比)
3. 普通方法和多執行緒
4. 程式、程序、執行緒
- 執行的程式就是程序
- 一個程序可以有多個執行緒,如:視訊中可以同時看影象,聽聲音,看彈幕。
- 程序包含至少一個執行緒,如果是模擬出的多執行緒,即在一個CPU中快速切換,感覺到是同時執行
5. 核心概念
- 執行緒就是獨立的執行路徑;
- 在程式執行時,即使沒有自己建立執行緒,後臺也會有多個執行緒,如主執行緒,gc執行緒;main()稱之為主執行緒,為系統的入口,用於執行整個程式;
- 在一個程序中,如果開闢了多個執行緒,執行緒的執行由排程器安排排程,排程器是與作業系統緊密相關的,先後順序是不能認為的干預的。
- 對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制;執行緒會帶來額外的開銷,如cpu排程時間,併發控制開銷。
- 每個執行緒在自己的工作記憶體互動,記憶體控制不當會造成資料不一致
二、執行緒建立
1. 三種建立方式
-
Thread class:繼承Thread類(重點)
-
package com.xiaowei9s.lesson01; //建立執行緒方式一:繼承Thread類,重寫run方法,呼叫start開啟執行緒 //總結:執行緒不一定start後直接執行,由CPU排程 public class ThreadTest1 extends Thread { public static void main(String[] args) { ThreadTest1 threadTest1 = new ThreadTest1(); threadTest1.start(); for (int i = 0; i < 200; i++) { System.out.println("main"+i); } } @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("run"+i); } } }
-
Runnable介面:實現Runnable介面(重點)
-
package com.xiaowei9s.lesson01; //建立執行緒方式一:繼承Thread類,重寫run方法,呼叫start開啟執行緒 //總結:執行緒不一定start後直接執行,由CPU排程 public class ThreadTest3 implements Runnable { public static void main(String[] args) { ThreadTest3 threadTest3 = new ThreadTest3(); new Thread(threadTest3).start(); for (int i = 0; i < 2000; i++) { System.out.println("main"+i); } } @Override public void run() { for (int i = 0; i < 2000; i++) { System.out.println("run"+i); } } }
初時多併發:
package com.xiaowei9s.lesson01; //多個執行緒同時操作一個物件 //發現問題,執行緒不安全了 //模擬同時買票 public class ThreadTest4 implements Runnable { private int ticketNum=10; public static void main(String[] args) { ThreadTest4 tt1 = new ThreadTest4(); ThreadTest4 tt2 = new ThreadTest4(); new Thread(tt1,"小明").start(); new Thread(tt1,"老師").start(); new Thread(tt1,"黃牛").start(); } @Override public void run() { while (ticketNum>=0){ try { Thread.sleep(200); }catch (Exception e){ System.out.println(e); } System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum+"號票"); ticketNum--; } } }
龜兔賽跑
package com.xiaowei9s.lesson01; //龜兔賽跑 //跑一百米 //兔子睡覺並且輸掉遊戲 public class ThreadTest5 implements Runnable{ private String winner; public static void main(String[] args) { ThreadTest5 threadTest5 = new ThreadTest5();//共用一個物件 new Thread(threadTest5,"兔子").start(); new Thread(threadTest5,"烏龜").start(); } private boolean gameOver(int step){//是否結束遊戲 if (winner!=null){ return true; } if (step==0){ winner = Thread.currentThread().getName(); System.out.println(winner+"贏了"); return true; } return false; } @Override public void run() { int step = 100;//雖然共用一個物件,但是方法是兩個,每個執行緒呼叫方法時方法時獨立得方法,方法內的資源不共用 while(true){ try {//延時 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if(gameOver(step)){ break; } if (Thread.currentThread().getName().equals("兔子")&&step%10==0){//兔子跑十下休息以下 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"還剩"+step+"步"); step--; } } }
-
Callable介面:實現Callable介面(瞭解)
-
package com.xiaowei9s.lesson01; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.*; /* 執行緒建立方式三,實現Callable介面 優勢: 1. 自定義返回值型別 2. 可以丟擲異常 */ public class ThreadTest6 implements Callable<Boolean> { public ThreadTest6(String name, String url) { this.name = name; this.url = url; } public String name; public String url; public static void main(String[] args) throws ExecutionException, InterruptedException { //建立執行緒池 ExecutorService exs = Executors.newFixedThreadPool(3); //提交執行緒 Future<Boolean> submit = exs.submit(new ThreadTest6("1.jpg", "https://pics6.baidu.com/feed/42a98226cffc1e17b881c7a94554b40a728de9bd.jpeg?token=6f6f69fad743d5bf129581dcbe35269f")); Future<Boolean> submit1 = exs.submit(new ThreadTest6("2.jpg","https://pics1.baidu.com/feed/4b90f603738da97767ecd34db795ba108418e3e6.jpeg?token=2695638344602480017c59554e078749")); Future<Boolean> submit2 = exs.submit(new ThreadTest6("3.jpg","https://pics3.baidu.com/feed/377adab44aed2e73899a0cee8ac5e38285d6faca.jpeg?token=2bc485aa1ae4cea267fb997488b209d5")); //獲得執行緒結果 Boolean aBoolean = submit.get(); Boolean aBoolean1 = submit1.get(); Boolean aBoolean2 = submit2.get(); //關閉服務 exs.shutdownNow(); } @Override public Boolean call() throws Exception { MyDownloader downloader = new MyDownloader(); downloader.download(url,name); return true; } class MyDownloader{ public void download(String url,String name){ try { FileUtils.copyURLToFile(new URL(url),new File(name)); System.out.println(name); } catch (IOException e) { e.printStackTrace(); } } } }
推薦使用Runnable介面,因為Runnable介面可以解耦,繼承Thread類有侷限性
三、靜態代理
讓主要的類專注於處理實現關鍵功能,例如
new Thread(()->System.out.println("run()方法中的事情")).start();
package com.xiaowei9s.lesson02;
public class StaticMode {
public static void main(String[] args) {
ToolMan toolMan = new ToolMan(new You());
toolMan.toDo();
}
}
class You implements Work{
@Override
public void toDo() {
System.out.println("你做了關鍵的事情");
}
}
class ToolMan implements Work{
public ToolMan(Work toWork) {
this.toWork = toWork;
}
public Work toWork;//把工具人幫助的人加入工具人,兩個人實現了同一個介面就是靜態代理
@Override
public void toDo() {
System.out.println("工具人先做了輔助的事情");
toWork.toDo();
}
}
interface Work{
public void toDo();
}
四、Lambda表示式
- 函式式介面:只包含唯一一個抽象方法,它就是一個函式式介面
- 對於函式式介面,我們可以使用Lambda表示式建立該介面的物件
package com.xiaowei9s.lesson02;
//逐漸匯出Lambda表示式
//實現類->匿名內部類->Lambda表示式
public class LambdaDemo {
public static void main(String[] args) {
//匿名內部類
FunMode fm = new FunMode() {
@Override
public void toDo() {
System.out.println("使用匿名內部類實現");
}
};
fm.toDo();
new Test1().toDo();
//Lambda表示式
fm = ()-> System.out.println("使用Lambda表示式實現");
fm.toDo();
}
interface FunMode{//函式式介面
public void toDo();
}
}
///使用實現類
class Test1 implements LambdaDemo.FunMode {
@Override
public void toDo() {
System.out.println("使用實現類實現");
}
}
五、執行緒停止
執行緒狀態:
執行緒方法:
1. 停止執行緒的方法
-
官方不建議使用JDK中的方法
-
使用一個停止標誌位實現執行緒停止
-
package com.xiaowei9s.lession03; public class TestStop { public static void main(String[] args) { Arun arun = new Arun(); new Thread(arun).start(); for (int i = 0; i < 1000; i++) { if (i==500){ arun.stop(); System.out.println("stop"); } System.out.println(i); } } } class Arun implements Runnable{ private boolean isStop = true; @Override public void run() { int i = 0; while (isStop){ System.out.println("I am running..."+i++); } System.out.println("ji"); } public void stop(){ this.isStop = false; } }
六、執行緒休眠
- sleep(時間)表示當前執行緒休眠時間,單位:毫秒
- sleep存在異常InterruptedExpection;
- sleep時間達到後,執行緒重新進入就緒狀態;
- sleep可以模擬網路延時、倒計時等等;
- 每一個物件都有鎖,sleep不會釋放鎖;(記住、不用理解)
Demo:模擬網路延遲:
package com.xiaowei9s.lession03;
//模擬網路延遲
public class TestSleep implements Runnable{
public int ticket = 10;
public static void main(String[] args) {
TestSleep testSleep = new TestSleep();
new Thread(testSleep,"小明").start();
new Thread(testSleep,"小紅").start();
new Thread(testSleep,"老師").start();
new Thread(testSleep,"黃牛").start();
}
@Override
public void run() {
while (ticket>=0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->買了第"+ticket+"號票");
ticket--;
}
}
}
Demo:模擬時鐘:
package com.xiaowei9s.lession03;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestTime {
public static void main(String[] args) throws InterruptedException {
while (true){
Date date = new Date(System.currentTimeMillis());
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
Thread.sleep(1000);
}
}
}
七、執行緒禮讓
- 讓當前程序從執行變成就緒狀態
- 當前程序不阻塞,依然擁有資源
- 不一定了禮讓成功,變成就緒的起跑線,cpu依然有可能讓禮讓的執行緒重新進入執行
package com.xiaowei9s.lession03;
public class TestYeild {
public static void main(String[] args) {
new Thread(new MyYield(),"A").start();
new Thread(new MyYield(),"B").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"執行緒開始");
Thread.yield();//禮讓
System.out.println(Thread.currentThread().getName()+"執行緒結束");
}
}
八、執行緒強制執行(執行緒插隊)
直接加入指定程序,插隊!
package com.xiaowei9s.lession03;
public class TestJoin implements Runnable {
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 500; i++) {
if (i==150){
thread.join();
}
System.out.println(i+"main");
}
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
九、執行緒狀態檢測
執行緒在停止後,不能再次start()。
package com.xiaowei9s.lession03;
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("THE END");
});
Thread.State state = thread.getState();
System.out.println(state);
thread.start();
while (state!=Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState();
System.out.println(state);
}
state = thread.getState();
System.out.println(state);
}
}
十、執行緒優先順序
數字越小,優先順序越低,10優先順序最高
main執行緒預設優先順序是5
優先順序只是意味著cpu排程概率,優先順序低也有概率會先執行
優先順序設定在start之前
package com.xiaowei9s.lession03;
public class TestPriority implements Runnable {
public static void main(String[] args) {
TestPriority testPriority = new TestPriority();
Thread a = new Thread(testPriority, "a");
Thread b = new Thread(testPriority, "b");
Thread c = new Thread(testPriority, "c");
Thread d = new Thread(testPriority, "d");
Thread e = new Thread(testPriority, "e");
Thread f = new Thread(testPriority, "f");
Thread g = new Thread(testPriority, "g");
Thread h = new Thread(testPriority, "h");
Thread i = new Thread(testPriority, "i");
System.out.println("main-->"+Thread.currentThread().getPriority());
a.setPriority(1);
b.setPriority(2);
c.setPriority(3);
d.setPriority(4);
e.setPriority(6);
f.setPriority(7);
g.setPriority(8);
h.setPriority(9);
a.start();
b.start();
c.start();
d.start();
e.start();
f.start();
g.start();
h.start();
i.start();
}
@Override
public void run() {
System.out.println("this Thread"+Thread.currentThread().getName()
+"-->"+Thread.currentThread().getPriority());
}
}
十一、守護執行緒Deamon
一旦執行緒被設定成守護執行緒,則主執行緒不會等待守護執行緒結束。
使用方法
thread.setDeamon(true);//將該執行緒設定成守護執行緒
Demo:
package com.xiaowei9s.lession03;
public class TestDeamon {
public static void main(String[] args) {
You you = new You();
God god = new God();
Thread gt = new Thread(god);
gt.setDaemon(true);
gt.start();
new Thread(you).start();
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 35000; i++) {
System.out.println("你活了:" + i + "天");
}
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("你的家人朋友守護著你");
}
}
}
十二、執行緒同步機制
多個執行緒操作一個資源
1. 併發
同一個物件被多個執行緒同時操作
在處理執行緒時多個執行緒同時訪問一個物件,這個時候我們需要執行緒同步。
執行緒同步其實就是等待機制,多個執行緒進入該物件的執行緒池中等待形成佇列,進行順序訪問。
2. 佇列和鎖
佇列:保證訪問順序
鎖:保證物件同一時間只能有一個物件訪問
鎖機制:synchronized,主要就是排它鎖
3. 效能問題
進行執行緒同步會導致效能下降和優先順序倒置問題
4. 三大不安全案例
-
不安全的買票
-
package com.xiaowei9s.syn; public class UnsafeTicket { public static void main(String[] args) { BuyTicket buyTicket = new BuyTicket(); new Thread(buyTicket,"小明").start(); new Thread(buyTicket,"小黃").start(); new Thread(buyTicket,"小紅").start(); } } class BuyTicket implements Runnable{ int numTicket = 10; boolean flag = true; //買票 public void buy(){ if (numTicket<=0){ flag = false; } System.out.println(Thread.currentThread().getName()+"買到了"+numTicket--+"票"); } @Override public void run() { while (flag){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } buy(); } } }
-
不安全的取錢
-
package com.xiaowei9s.syn; public class UnsafeBank { public static void main(String[] args) { Account account = new Account("結婚基金", 1000); Drawing d1 = new Drawing(account, 500, "老公"); Drawing d2 = new Drawing(account, 800, "老婆"); d1.start(); d2.start(); } } //賬戶 class Account{ public String name; public int monny; public Account(String name , int monny){ this.name = name; this.monny = monny; } } //銀行取錢 class Drawing extends Thread{ Account account; int nowMonny; int getMonny; public Drawing(Account account, int getMonny, String name){ super(name); this.account = account; this.getMonny = getMonny; } //取錢 @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } if (account.monny - getMonny < 0){ System.out.println("賬戶裡還有" + account.monny + "元,錢不夠了"); return; } nowMonny += getMonny; account.monny -= getMonny; System.out.println(Thread.currentThread().getName()+"取了" + getMonny+"元,還剩"+account.monny+"元,現在ta有" + nowMonny+"元"); } }
-
不安全的列表
-
package com.xiaowei9s.syn; import java.util.ArrayList; public class UnsafeList { public static void main(String[] args) throws InterruptedException { ArrayList arrayList = new ArrayList(); for (int i = 0; i < 10000; i++) { new Thread(()->{ arrayList.add(1); }).start(); } Thread.sleep(3000); System.out.println(arrayList.size()); } }
5. 同步方法
在方法中加入同步方法關鍵字synchronized,這個方法就變成了同步方法,這個方法只能在獲得物件的鎖的時候才能執行,執行完成才返回這把鎖。這把鎖鎖的是this,也就是方法體所在的物件。
缺陷:影響效率
比如之前的買票,在run方法加入關鍵字即可同步
package com.xiaowei9s.syn;
public class UnsafeTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"小明").start();
new Thread(buyTicket,"小黃").start();
new Thread(buyTicket,"小紅").start();
}
}
class BuyTicket implements Runnable{
int numTicket = 10;
boolean flag = true;
//買票
public void buy(){
System.out.println(Thread.currentThread().getName()+"買到了第"+numTicket--+"票");
if (numTicket==0){
flag = false;
}
}
@Override
public synchronized void run() {//加入關鍵字
while (flag){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
buy();
}
}
}
6. 同步塊
synchronized(Obj){};//Obj是需要監視的物件,也就是對這個物件加上鎖
例如銀行取錢,我們只需要對賬戶加上監視,讓它同步即可:
package com.xiaowei9s.syn;
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account("結婚基金", 1000);
Drawing d1 = new Drawing(account, 500, "老公");
Drawing d2 = new Drawing(account, 800, "老婆");
d1.start();
d2.start();
}
}
//賬戶
class Account{
public String name;
public int monny;
public Account(String name , int monny){
this.name = name;
this.monny = monny;
}
}
//銀行取錢
class Drawing extends Thread{
Account account;
int nowMonny;
int getMonny;
public Drawing(Account account, int getMonny, String name){
super(name);
this.account = account;
this.getMonny = getMonny;
}
//取錢
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (account){
if (account.monny - getMonny < 0){
System.out.println("賬戶裡還有" + account.monny + "元,錢不夠了");
return;
}
nowMonny += getMonny;
account.monny -= getMonny;
System.out.println(Thread.currentThread().getName()+"取了"
+ getMonny+"元,還剩"+account.monny+"元,現在ta有"
+ nowMonny+"元");
}
}
}
7. CopyOnWriteArrayList
就是一個JUC併發程式設計中的一個列表類。
他內部實現了一個鎖和同步的功能,不用自己去實現同步。
十三、死鎖
當一個同步塊擁有兩個以上的物件的鎖的時候有可能發生。
因為一個物件被一個執行緒佔有,而另一個物件被另一個執行緒佔有,兩個執行緒都不願意放手,就誰都做不了事情。
package com.xiaowei9s.syn;
public class DieLock {
public static void main(String[] args) {
Key1 key1 = new Key1();
Key2 key2 = new Key2();
LockNeedK1K2 lockNeedK1K2 = new LockNeedK1K2(key1, key2);
new Thread(lockNeedK1K2,"小紅").start();
new Thread(lockNeedK1K2,"小黃").start();
}
}
class LockNeedK1K2 implements Runnable{
public LockNeedK1K2(Key1 k1, Key2 k2) {
this.k1 = k1;
this.k2 = k2;
}
public Key1 k1;
public Key2 k2;
@Override
public void run() {
if(Thread.currentThread().getName().equals("小紅")){
synchronized (k1){
System.out.println(Thread.currentThread().getName() + "獲得了第一個密碼");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (k2){
System.out.println(Thread.currentThread().getName() + "獲得了第二個密碼");
System.out.println("密碼是:" + k1.password1 + k2.password2);
System.out.println("打開了寶箱");
}
}
}
if(Thread.currentThread().getName().equals("小黃")){
synchronized (k2){
System.out.println(Thread.currentThread().getName() + "獲得了第二個密碼");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (k1){
System.out.println(Thread.currentThread().getName() + "獲得了第一個密碼");
System.out.println("密碼是:" + k1.password1 + k2.password2);
System.out.println("打開了寶箱");
}
}
}
}
}
class Key1{
String password1 = "123";
}
class Key2{
String password2 = "321";
}
避免死鎖:不要在同步塊中再次嵌入同步塊!
十四、Lock(鎖)
demo:
package com.xiaowei9s.syn;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
Lock1 lock1 = new Lock1();
new Thread(lock1,"hong").start();
new Thread(lock1,"huang").start();
new Thread(lock1,"jun").start();
}
}
class Lock1 implements Runnable{
public int ticket = 10;
public ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
lock.lock();
while (ticket>0){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ticket);
ticket--;
}
}catch (Exception e){
}finally {
lock.unlock();
}
}
}
十五、執行緒協作
生產者消費者問題
相關方法:
解決方法:
-
緩衝區法,設定緩衝區(管程法)
-
package com.xiaowei9s.syn; public class TestPC { public static void main(String[] args) { SynContain synContain = new SynContain(); new Producer(synContain).start(); new Customer(synContain).start(); } } class Producer extends Thread{ public SynContain synContain; public Producer(SynContain synContain){ this.synContain = synContain; } //生產 @Override public void run() { for (int i = 0; i < 100; i++) { try { synContain.add(i); System.out.println("生產了第" + i + "只雞"); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Customer extends Thread{ public SynContain synContain; public Customer(SynContain synContain){ this.synContain = synContain; } //消費 @Override public void run() { for (int i = 0; i < 100; i++) { try { synContain.minus(i); System.out.println("消費了第" + i + "只雞"); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Chichen{ public Chichen(int id) { this.id = id; } int id; } class SynContain{ Chichen[] contain = new Chichen[10]; int count = -1; public synchronized void add(int i) throws InterruptedException { if(count+1<contain.length){ contain[++count] = new Chichen(i); this.notifyAll(); }else if (count>=10){ this.wait(); } } public synchronized void minus(int i) throws InterruptedException { if(count>=0){ contain[count] = null; count--; this.notifyAll(); }else if (count==0){ this.wait(); } } }
-
訊號燈法:設定訊號用於喚醒或等待
-
package com.xiaowei9s.syn; public class TestPC2 { public static void main(String[] args) { Food food = new Food(); new Eater(food).start(); new Cooker(food).start(); } } //廚師 class Cooker extends Thread{ Food food; public Cooker(Food food) { this.food = food; } @Override public void run() { for (int i = 0; i < 20; i++) { try { food.cook(); } catch (InterruptedException e) { e.printStackTrace(); } } } } //顧客 class Eater extends Thread{ Food food; public Eater(Food food) { this.food = food; } @Override public void run() { for (int i = 0; i < 20; i++) { try { food.eat(); } catch (InterruptedException e) { e.printStackTrace(); } } } } //菜品 class Food{ String name = "滿漢全席"; boolean flag = false; public synchronized void eat() throws InterruptedException { if (!flag){ this.wait(); } System.out.println("顧客在吃"+name); flag = !flag; this.notifyAll(); } public synchronized void cook() throws InterruptedException { if (flag){ this.wait(); } System.out.println("廚師在做"+name); flag = !flag; this.notifyAll(); } }
十六、使用執行緒池
使用:
demo:
package com.xiaowei9s.syn;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.shutdownNow();
}
}
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
知識來源:kuangstudy.com
慢慢來慢慢來