Tread多執行緒
什么是執行緒?
-
執行緒(Thread)是一個程式內部的一條執行流程,
-
程式中如果只有一條執行流程,那這個程式就是單執行緒的程式,
多執行緒是什么?
多執行緒是指從軟硬體上實作的多條執行流程的技術(多條執行緒由cpu負責調度執行),
多執行緒的創建方式
方式一:繼承Thread
①定義一個子類MyThread繼承執行緒類java.lang.Thread,重寫run()方法
②創建MyThread類的物件
③呼叫執行緒物件的start()方法啟動執行緒(啟動后還是執行run方法的)
//(1)讓自定義的MyThread繼承Thread執行緒類【自定義的類也就具備執行緒的特性】 public class MyThread extends Thread { //(2)想要宣告自定義的執行緒執行的時候到底執行什么代碼,主動重寫父類的run方法 @Override public void run() { for (int i = 1; i <= 20; i++) { System.out.println("【自定義執行緒】的run方法執行了第" + i + "次!"); } } } public class ThreadTest1 { public static void main(String[] args) { //(3)創建自定義執行緒類物件并呼叫start方法啟動執行緒 MyThread myThread = new MyThread(); myThread.start(); ? //補:在自定義執行緒啟動之后,繼續撰寫代碼讓主執行緒執行 for (int i = 1; i <= 20; i++) { System.out.println("【主執行緒】的run方法執行了第" + i + "次!"); } } }
方式一優缺點
-
優點:編碼簡單
-
缺點:執行緒類已經繼承Thread,無法繼承其他類,不利于功能的擴展,
方式二:實作Runnable介面
①定義一個執行緒任務類MyRunnable實作Runnable介面,重寫run()方法
②創建MyRunnable任務物件
③把MyRunnable任務物件交給Thread處理,
public class ThreadTest2 { public static void main(String[] args) { //(3)創建MyRunnable執行緒任務物件 【和執行緒還沒有關系】 MyRunnable myRunnable = new MyRunnable(); //(4)創建Thread執行緒物件,并且執行緒任務作為構造方法的引數傳遞【槍:√ 彈夾:√】 Thread t = new Thread(myRunnable); t.start(); ? //補:在自定義執行緒啟動之后,繼續撰寫代碼讓主執行緒執行 for (int i = 1; i <= 20; i++) { System.out.println("【主執行緒】的run方法執行了第" + i + "次!"); } } }
方式二優缺點:
-
優點:任務類只是實作介面,可以繼續繼承其他類,實作其他介面,擴展性強,
-
缺點:需要多一個Runnable物件,
前兩種執行緒創建檔案都存在的一個問題
假如執行緒執行完畢后有一些資料需要回傳,他們重寫的run方法均不能直接回傳結果,
解決(多執行緒的第三種創建方式)
利用Callable,FutureTask型別實作,
①創建任務物件
定義一個類實作Callable介面,重寫call方法,封裝要做的事情和要回傳的資料,
把Callable型別的物件封裝成FutureTask物件(執行緒任務物件),
②把執行緒任務物件封裝成Thread物件,
③呼叫Thread物件的start方法啟動執行緒,
④執行緒執行完畢后,通過FutureTask物件的的get方法去獲取執行緒任務執行的結果,
public class ThreadTest4 { public static void main(String[] args) throws ExecutionException, InterruptedException { //(3)Thread類不支持直接傳遞一個Callable執行緒任務物件【封裝FutureTask物件并且將Callable執行緒任務作為構造引數傳遞】 MyCallable myCallable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<>(myCallable); ? //(4)創建Thread類物件并且將FutureTask作為引數傳遞 Thread t = new Thread(futureTask); t.start(); ? //★(5)通過futureTask物件獲取結果 Integer result = futureTask.get(); System.out.println("帶有回傳值的執行緒任務執行完成后回傳的結果是:" + result); ? ? //補:在自定義執行緒啟動之后,繼續撰寫代碼讓主執行緒執行 for (int i = 1; i <= 20; i++) { System.out.println("【主執行緒】的run方法執行了第" + i + "次!"); } } }
方式三優缺點:
-
優點:執行緒任務類只是實作介面,可以繼續繼承類和實作介面,擴展性強;可以在執行緒執行完畢后去獲取執行緒執行的結果,
-
缺點:編碼復雜一點,
三種執行緒創建方法比較

Thread的常見用法
public void run() 執行緒的任務方法
public void start() 啟動執行緒
public Strign getName() 獲取1當前執行緒名稱,執行緒名稱默認是Thread-索引
public void setName(String name) 為執行緒設定名稱
public static Thread currentThread() 獲取當前執行的執行緒物件
public static void sleep(long time) 讓當前執行的執行緒休眠多少毫秒后,再繼續執行
public void join() 讓呼叫這個方法的執行緒先執行完
public class MyRunRunable implements Runnable { @Override public void run() { for (int i = 1; i <= 200; i++) { //在列印的時候,想要【獲取到執行當前這行代碼的執行緒】的執行緒名稱 //通過★Thread.currentThread():獲取當前執行此方法的執行緒物件 System.out.println(Thread.currentThread().getName() + "已經跑了" + i + "米!"); } } } ? ? public class ThreadTest5 { public static void main(String[] args) throws ExecutionException, InterruptedException { ? String threadName = Thread.currentThread().getName(); System.out.println("【主執行緒名稱】:" + threadName); ? MyRunRunable myRunRunable = new MyRunRunable(); //執行緒起名方式(1):通過執行緒物件呼叫setName方法傳遞名稱 Thread t1 = new Thread(myRunRunable); t1.setName("張二狗"); //思考:模擬兩個人跑 => 兩個執行緒跑【跑的邏輯一樣 所以使用同一個執行緒任務】不會干擾【底層:執行緒堆疊 執行緒執行程序中產生的變數資料都在執行緒堆疊中保存】 ? //執行緒起名方式(2):通過new Thread構造方法的時候,將引數一作為執行緒任務,引數二作為執行緒名稱 Thread t2 = new Thread(myRunRunable, "劉鐵柱"); t1.start(); //t1.join(); 【讓呼叫此方法的執行緒先執行完:插隊】 t2.start(); } }
執行緒安全
什么是執行緒安全問題?
多個執行緒,同時操作同一個共享資源的時候,可能會出現業務安全問題,
取錢的執行緒安全問題
場景:小明和小紅是一對夫妻,他們有一個共同的賬戶,余額是10萬元,如果小明和小紅同時來取錢,并且2人各自都在取錢10萬元,可能會出現什么問題呢?

執行緒安全問題出現的原因?
-
存在多個執行緒在同時執行
-
多個執行緒同時訪問一個共享資源
-
存在修改共享資源的情況
定義一個賬號類
package com.itheima.safe; ? import java.time.LocalTime; ? public class Account { private String accoundId; private Integer money; //取錢:takeMoney public void takeMoney(Integer money) { //獲取當前取錢的執行緒名稱 String name = Thread.currentThread().getName(); System.out.println(LocalTime.now() + " " + name + "準備開始取錢!"); if (this.money >= money) { System.out.println(LocalTime.now() + " " + name + "取出了" + money + "元!"); this.money -= money; } else { System.out.println(LocalTime.now() + " " + name + "余額不足!"); } System.out.println(LocalTime.now() + " 賬戶的余額是:" + this.money + "元!"); } ? public String getAccoundId() { return accoundId; } ? public void setAccoundId(String accoundId) { this.accoundId = accoundId; } ? public Integer getMoney() { return money; } ? public void setMoney(Integer money) { this.money = money; } ? public Account() { } ? public Account(String accoundId, Integer money) { this.accoundId = accoundId; this.money = money; } }
?
定義執行緒類
?
public class TakeMoneyRunnable implements Runnable { //執行緒任務需要訪問到Account賬戶物件【將賬戶物件作為執行緒任務的構造方法 并且只給出一個有參構造】 private Account account; public TakeMoneyRunnable(Account account) { this.account = account; } @Override public void run() { account.takeMoney(100000); } }
測驗類
package com.itheima.safe; ? public class TakeMoneyThreadTest { public static void main(String[] args) { Account account = new Account("CHINA-BANK-62261728738", 100000); //創建執行緒任務【由于兩個執行緒的邏輯一樣 只需要一個執行緒任務】 TakeMoneyRunnable takeMoneyRunnable = new TakeMoneyRunnable(account); //創建執行緒物件并且傳遞執行緒任務和執行緒名稱 Thread t1 = new Thread(takeMoneyRunnable, "張二狗"); Thread t2 = new Thread(takeMoneyRunnable, "王美麗"); t1.start(); t2.start(); } } ?
執行緒同步(解決執行緒安全)
執行緒同步的思想
讓多個執行緒實作先后依次訪問共享資源,這樣就解決了安全問題,
同步代碼塊
作用:把訪問共享資源的核心代碼給上鎖,以此保證執行緒安全,

原理:每次只允許一個執行緒加鎖后進入,執行完畢后自動解鎖,其他執行緒次才可以來執行,
對執行緒安全改造
public void takeMoney(Integer money) { String name = Thread.currentThread().getName(); System.out.println(LocalDateTime.now() + "" + name + "準備開始取錢"); //(同步代碼塊)加鎖 synchronized (this) { if (this.money >= money) { System.out.println(LocalDateTime.now() + "" + name + "取出了" + money + "元"); this.money -= money; } else { System.out.println(LocalDateTime.now() + "" + name + "余額不足"); } System.out.println(LocalDateTime.now() + "賬戶的余額是" + this.money + "元"); } }
同步鎖的注意事項
-
對于當前同時執行的執行緒來說,同步鎖必須是同一把鎖(同一個物件),否則會出bug,
鎖物件的使用規范
-
建議使用共享資源作為鎖物件,對于實體方法建議使用this作為鎖物件,
-
對于靜態方法建議使用位元組碼(類名.class)物件作為鎖物件,
同步方法
作用:把訪問共享資源的核心代碼給上鎖,以此保證執行緒安全,

原理:每次只允許一個執行緒加鎖后進入,執行完畢后自動解鎖,其他執行緒次才可以來執行,
對執行緒安全改造
//同步方法加鎖(修飾符后面,放回值型別前面) public synchronized void takeMoney(Integer money) { String name = Thread.currentThread().getName(); System.out.println(LocalDateTime.now() + "" + name + "準備開始取錢"); ? if (this.money >= money) { System.out.println(LocalDateTime.now() + "" + name + "取出了" + money + "元"); this.money -= money; } else { System.out.println(LocalDateTime.now() + "" + name + "余額不足"); } System.out.println(LocalDateTime.now() + "賬戶的余額是" + this.money + "元"); } }
同步方法底層原理
-
同步方法其實底層也是有隱式鎖物件的,只是鎖的范圍是整個方法代碼,
-
如果方法是實體方法:同步方法默認用this作為的鎖物件,
-
如果方法是靜態方法:同步方法默認用類名.class作為的鎖物件,
1.同步方法是如何保證執行緒安全的?
-
對出現問題的核心方法使用**synchronized修飾**
-
每次只能一個執行緒占鎖進入訪問
2.同步方法的同步鎖物件的原理?
-
對于實體方法默認使用**this作為鎖物件,**
-
對于靜態方法默認使用**類名.class物件作為鎖物件,**
Lock鎖
Lock鎖是JDK5開始提供的一個新的鎖定操作,通過它可以創建出鎖物件進行加鎖和解鎖,更靈活、更方便、更強大,
Lock是介面,不能直接實體化,可以采用它的實作類ReentrantLock來構建Lock鎖物件,
對執行緒安全改造
private static final Lock LOCK = new ReentrantLock(); public void takeMoney(Integer money) { ? LOCK.lock(); ? try { String name = Thread.currentThread().getName(); System.out.println(LocalDateTime.now() + "" + name + "準備開始取錢"); if (this.money >= money) { System.out.println(LocalDateTime.now() + "" + name + "取出了" + money + "元"); this.money -= money; } else { System.out.println(LocalDateTime.now() + "" + name + "余額不足"); } System.out.println(LocalDateTime.now() + "賬戶的余額是" + this.money + "元"); }finally { LOCK.unlock(); } } ?
執行緒池
了解執行緒池
什么是執行緒池?
執行緒池就是一個可以復用執行緒的技術,
不使用執行緒池的問題
用戶每發起一個請求,后臺就需要創建一個新執行緒來處理,下次新任務來了肯定又要創建新執行緒處理,而創建新執行緒的開銷是很大的,并且請求過多時,肯定會產生大量的執行緒,這樣會嚴重影響系統的性能,
執行緒池的作業原理

創建執行緒池
如何得到執行緒池物件?
方式一:使用ExecutorService的實作類ThreadPoolExecutor創建一個執行緒池物件,

方式二:使用Executors(執行緒池的工具類)呼叫方法回傳不同特點的執行緒池物件,
ThreadPoolExecutor**構造器**

-
引數一:corePoolSize : 指定執行緒池的核心執行緒數量,
-
引數二:maximumPoolSize:指定執行緒池的最大執行緒數量,
-
引數三:keepAliveTime :指定臨時執行緒的存活時間,
-
引數四:unit:指定臨時執行緒存活的時間單位(秒、分、時、天)
-
引數五:workQueue:指定執行緒池的任務佇列,
-
引數六:threadFactory:指定執行緒池的執行緒工廠,
-
引數七:handler:指定執行緒池的任務拒絕策略(執行緒都在忙,任務佇列也滿了的時候,新任務來了該怎么處理)
public class PoolDemo1 { public static void main(String[] args) { //基于ThreadPoolExecutor的構造方法創建執行緒池物件 //核心執行緒:【執行緒任務:Cpu密集型(運算):當前機器Cpu的核心數+1 Runtime.getRuntime().availableProcessors()+1】 //核心執行緒:【執行緒任務:IO密集型(讀寫):當前機器Cpu的核心數*2 Runtime.getRuntime().availableProcessors()*2】 //ThreadFactory:執行緒工廠 【Exectors.defaultThreadFactory】 獲取默認的執行緒工廠 ThreadPoolExecutor pool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors() + 1, 15, 40L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); ? //可以基于執行緒池規范介面的execute方法提交執行緒任務交給執行緒池執行 for (int i = 1; i <= 25; i++) { pool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "執行了執行緒任務!"); } }); } ? //AbortPolicy:默認丟棄新任務并且拋出例外 //DiscardPolicy:默認丟棄新任務并且不拋出例外 //DiscardOldestPolicy:默認將等待時間最長的任務丟棄,并且讓新任務添加到佇列中 //CallerRunsPolicy:使用主執行緒執行新任務繞過當前執行緒池 ? //執行緒池一旦提交任務就持久運行【想要關閉呼叫shutdown/shutdownNow】 pool.shutdown(); } }
執行緒池的注意事項
1、臨時執行緒什么時候創建?
新任務提交時發現核心執行緒都在忙,任務佇列也滿了,并且還可以創建臨時執行緒,此時才會創建臨時執行緒,
2、什么時候會開始拒絕新任務?
核心執行緒和臨時執行緒都在忙,任務佇列也滿了,新的任務過來的時候才會開始拒絕任務,
執行緒池如何處理Runnable任務?
-
使用ExecutorService的方法:
-
void execute(Runnable target)
執行緒池如何處理Callable任務,并得到任務執行完后回傳的結果?
-
使用ExecutorService的方法:
-
Future<T> submit(Callable<T> command)
并發,并行
并發的含義
行程中的執行緒是由CPU負責調度執行的,但CPU能同時處理執行緒的數量有限,為了保證全部執行緒都能往前執行,CPU會輪詢為系統的每個執行緒服務,由于CPU切換的速度很快,給我們的感覺這些執行緒在同時執行,這就是并發,
并行的理解
在同一個時刻上,同時有多個執行緒在被CPU調度執行,
簡單說說多執行緒是怎么執行的?
-
并發:CPU分時輪詢的執行執行緒,
-
并行:同一個時刻多個執行緒同時在執行,
執行緒的生命周期
Java執行緒的狀態
-
Java總共定義了6種狀態
-
6種狀態都定義在Thread類的內部列舉類中,



轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/546187.html
標籤:其他
上一篇:python的基本認識
