Java 多线程Thread
生活随笔
收集整理的這篇文章主要介紹了
Java 多线程Thread
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
重點:?
- java中多線程運行原理
- 掌握兩種線程創建方式
- 兩種創建線程方式好處和弊端
- 掌握使用Thread類 中獲取和設置線程名稱的方法
- 使用匿名內部類創建多線程
- 描述java中線程池的運行原理
- 線程安全問題出現的原因
- 使用同步代碼塊\同步方法解決線程安全問題
- 出現死鎖的原因
- wait方法
- notify方法
- 線程的五個狀態
一、概述
- 進程: 進程是指正在運行的程序。 確切的來說,當一個程序進入內存開始運行,即變成一個進程。 進程是處于運行狀態的程序,并具有一定獨立功能。
- 線程: 線程是進程的一個執行單元。 負責當前進程中程序的執行。一個進程中至少有一個線程,一個進程可以是多線程的。
- 多線程程序與單線程程序:
- 單線程程序 : 若有多個任務只能一次執行。
- 多線程程序 : 若有多個任務,可以同時執行。
二、多線程運行原理
- 分時調度 : 所有線程輪流使用CPU 的使用權 , 平均分配每個線程的占用CPU時間。
- 搶占式調度 ?: 優先讓優先值高的線程使用Cpu , 如果線程的優先級相同 , 則隨機選擇一個 (**線程的隨機性**)。
- java 使用的是搶占式調度。?
三、搶占式調度詳解
- 實際上 , CPU使用搶占式調度模式是在多個線程之間進行著高速的切換。 對于CPU的一個核而言 , 在某一時刻只能執行一個進程 。 而CPU在多個線程之間的切換速度相對于我們而言相當的塊 , 看上去是多個線程同時執行。
- 多線程不能提高程序的執行速度 , ?但是能提高CPU的使用率。
四、主線程
- JVM啟動后 , 必然有一個執行路徑(線程)從mian方法開始 , 一直執行到main方法執行結束 , 這個線程在java中 稱之為主線程。
- 示例:package com.tj.ThreadTest;public class ThreadTest_01 {public static void main(String[] args) {class_01 c1 = new class_01("催化");class_01 c2 = new class_01("淑芬");c1.show();c2.show();}
}
class class_01{private String name;class_01(String name){this.name = name;}public void show(){for(int i= 0;i<1000;i++){System.out.println(this.name+","+i);}}
}
五、Thread類
- 構造方法:
- Thread() : ?創建一個新線程。
- Thread() : 創建一個名為name的線程。
- 常用函數:
- start() : 使線程開始執行 , 由JVM調用Thread‘中的run方法。
- run() : 該線程要之心的操作
- sleep() : 在指定的毫秒數之內讓當前正在執行的線程休眠(暫停執行)。
- ’創建線程的兩種方法:?
- 將該類聲明為Thread類的子類。 該類必須重寫Therad類的run方法 。 創建對象 , 開啟線程。 run方法相當于主線程的main方法
- 該類實現Runnable接口: ?實現run方法 , 創建Runnable子類對象并傳入到某個線程的構造方法中 , 開啟線程。
六、創建線程方式 ?--- 繼承Thread類
- 第一步: 定義一個類 , 繼承Thread類
- 第二步: 重寫run方法
- 第三步:創建子類 對象
- 第四步: 調用start方法 , 開啟線程并執行 ?, 此時JVM會自動調用run方法。
- 示例:?package com.tj.ThreadTest;public class ThreadTest_02 {public static void main(String[] args) {//創建線程對象ThreadClass_01 ti = new ThreadClass_01("線程1");//開啟線程ti.start();}
}
//使用繼承Thread類的方式創建一個線程類
class ThreadClass_01 extends Thread{private String name;public ThreadClass_01(String name) {this.name = name;}//重寫run方法@Overridepublic void run() {for(int i =0;i<1;i++){System.out.println(name +","+i);//獲取當前對象線程System.out.println(Thread.currentThread());System.out.println(Thread.currentThread().getName());}}}
- 思考: run方法與start方法的區別?
- 線程對象調用run方法不能開啟線程 。 僅僅是對象調用方法 。?
- 線程對象調用start方法 ,開啟線程 , 并讓JVM調用run方法在開啟的線程中執行。
- 繼承Therad類原理
- Thread類用于描述線程 , 具備線程應有的功能 。?
- 創建線程的目的是為了建立程序單獨執行的路徑, 讓多部分代碼同時執行 。 ?也就是說線程的創建并執行 需要給定線程 執行的任務。
- 程序中的主線程 , 任務定義在main中 ?。 自定義線程需要執行的任務定義在run中
- Thread類中本身的run方法并不是我們所期望的 , 所以要重寫run方法 , 重新制定線程任務。
- 多線程圖解
- 多線程執行在棧內存中 , 其實每一個執行線程都有一片屬于自己的棧內存空件 , 進行方法的彈棧和壓棧。
- 當線程任務執行完成之后 , 自動釋放棧內存 , 當所有的線程執行完畢之后, 進程結束。
- ?獲取線程的名稱
- currentThread() : 返回當前正在執行的線程的實例
- getName() : 獲取當前實例的名稱
- 主線程的名稱 : main?
- 自定義線程的名稱: Thread -0 ? ,如果有多個線程時 , 數字順延。 ? ?。。。 Thread - ?1 。。。。?
- 示例://使用繼承Thread類的方式創建一個線程類
class ThreadClass_01 extends Thread{private String name;public ThreadClass_01(String name) {this.name = name;}//重寫run方法@Overridepublic void run() {for(int i =0;i<1;i++){System.out.println(name +","+i);//獲取當前對象線程System.out.println(Thread.currentThread());System.out.println(Thread.currentThread().getName());}}}
七、創建線程方式 ?--- 實現Runnable接口
- 第一步 :定義一個類 , 實現Runnable接口
- 第二步 :實現run方法
- 第三步 :將該類的實例對象傳入到線程的構造函數中。
- 第四步 :開啟線程
- 示例: package com.tj.ThreadTest;public class ThreadTest_03 {public static void main(String[] args) {//創建實現Runnable接口的類對象ThreadClass_03 tc = new ThreadClass_03();//將實現實例一參數形式傳入到Thread類的構造函數中Thread t1 = new Thread(tc);Thread t2 = new Thread(tc,"線程2");//可以指定該線程的名字//啟動線程t1.start();t2.start();} } //創建實現Runnable接口的類 class ThreadClass_03 implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"線程被執行");}}
- 實現Runnable原理 , 兩種方式的區別:
- 實現Runnable接口 , 避免了單繼承的局限性 , 覆蓋Runnable接口中run方法 , 將線程任務定義在run方法中。
- 只有創建Thread類對象時才能創建新的線程 , 線程任務已經被封裝在RUnnable接口中 。 ?所以可以將實現Runnable接口的實例對象以參數的形式傳到Thread構造函數中。 這樣線程創建時就可以明確指定要運行的線程任務。
- 實現Runnable接口的好處
- 避免了單繼承的局限性
- 更加符合面向對象 。 ?線程分為兩部分 ?: 線程對象 , 線程任務 ? ; ?而繼承Thread之后 , 線程對象和線程任務耦合在一起
- 實現Runnable接口 , 將任務單獨分離出類 封裝成對象 , 是線程任務解耦。
- 線程匿名內部類的使用
- 方式一:創建線程對象 ,直接重寫run方法。
- 方式二:使用匿名內部類實現RUnnable接口后傳入THread構造函數。
- 示例 :?package com.tj.ThreadTest; //線程匿名內部類的使用 public class ThreadTest_04 {public static void main(String[] args) {//方式一new Thread(){@Overridepublic void run() {System.out.println("線程1開啟");}}.start();//方式二new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"開啟");}},"線程2").start();} }
八、線程池
- 概述:
- 可以容納多個線程的容器 , 其中的線程可以反復使用 。 省去了頻繁創建線程對象的操作 。 節省資源。
- 為什么要使用線程池?
- 在java中如果每個請求到達都開啟一個線程 , 開銷是相當大的 , 在實際開發中 , 創建和銷毀線程花費的時間和消耗掉的資源都相當大 , 甚至超過實際處理任務請求的時間和資源。
- 如果JVM里創建了太多的線程 , 可能會使系統由于過度消耗內存或“”切換過度“導致系統資源不足 , 為了防止系統資源不足的情況 ? , 需要采取一些辦法 來限定任何給定時間處理的請求 數目。
- 宗上述兩點 , 所以需要盡可能的減少線程的創建的銷毀次數 , 并盡量使用已有的對象進行服務。
- 線程池主要用于解決線程聲明周期開銷和系統資源不足的問題。由于請求到來的時候 , ?線程已經存在 , 所以消除了創建線程帶來的延時, 立即為請求服務 。 使程序響應更快。?
- ”使用線程池的方式 ---- 實現Runnable接口
- 通常線程池都是通過線程工廠創建 , 在調用線程池中的方法和數據 , 通過線程去執行任務 。?
- 創建線程池
- Executors線程池創建工廠類
- ExecutorService 線程池類
- Furture接口 ? 用來記錄線程任務執行完畢后產生的結果 ?
- 使用線程池的步驟:
- 創建線程池對象
- 創建Runnable子類對象
- 提交Runnabel子類對象
- 關閉線程池
- 示例:?package com.tj.ThreadTest;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** 使用線程池方式 --- Runnable* */
public class ThreadTest_05 {public static void main(String[] args) {//創建線程池對象, 指定包含幾個線程。ExecutorService es = Executors.newFixedThreadPool(3);//創建實現Runnable接口的子類對象ThreadClass_05 t5 = new ThreadClass_05();//提交線程任務es.submit(t5,"周教練");es.submit(t5,"吳教練");es.submit(t5,"正教練");es.submit(t5,"趙教練");es.submit(t5,"王教練");/*注意: submit 方法調用結束后,程序并不終止,因為線程池控制了線程的關閉*將使用完的線程有歸還到線程池中 * *///關閉線程池es.shutdown();}
}
class ThreadClass_05 implements Runnable{@Overridepublic void run() {System.out.println("我需要一個教練");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("教練來了"+Thread.currentThread().getName());System.out.println("叫我游泳之后教練回到了游泳池!");}}
- 使用線程池方式 ?--- Callable接口
- Callable 接口 : 與Runnable接口功能相似 。 用類指定線程任務 , ?其中call方法 用來返回線程任務執行后返回的結果 ? , call方法可以拋出異常。
- ExecutorService : 線程池類 , 獲取一個線程池對象 , 并執行線程的call方法
- Future接口 : 用來記錄線程對象執行完畢之后產生的結果 。?
- 使用線程池對象的步驟:
- 創建線程池對線
- 創建Callable子類對象
- 提交Callable子類對象
- 關閉線程池
- 示例:package com.tj.ThreadTest;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;/*** 使用線程池方式 -- callable* */
public class ThreadTest_07 {public static void main(String[] args) throws InterruptedException, ExecutionException {//創建線程池ExecutorService es = Executors.newFixedThreadPool(2);//創建縣線程任務ThreadDemo_07 t1 = new ThreadDemo_07();//提交線程任務Future<String> submit = es.submit(t1);System.out.println(submit.get());//關閉線程池es.shutdown();}
}
class ThreadDemo_07 implements Callable<String> {@Overridepublic String call() throws Exception {return "絕對符合國家";}}
- 線程池練習: 返回兩個數相加的結果package com.tj.ThreadTest;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;/*** 線程池練習 ,返回兩個數相加的結果* */
public class ThreadTest_08 {public static void main(String[] args) throws InterruptedException, ExecutionException {ExecutorService es = Executors.newFixedThreadPool(2);ThreadDemo_08 t1 = new ThreadDemo_08(1, 1);ThreadDemo_08 t2 = new ThreadDemo_08(6, 1);ThreadDemo_08 t3 = new ThreadDemo_08(3, 1);ThreadDemo_08 t4 = new ThreadDemo_08(2, 1);Future<Integer> s1 = es.submit(t1);Future<Integer> s2 = es.submit(t2);Future<Integer> s3 = es.submit(t3);Future<Integer> s4 = es.submit(t4);System.out.println(s1.get());System.out.println(s2.get());System.out.println(s3.get());System.out.println(s4.get());es.shutdown();}
}
class ThreadDemo_08 implements Callable<Integer>{int a = 0,b = 0;public ThreadDemo_08(int a, int b) {this.a = a;this.b = b;}@Overridepublic Integer call() throws Exception {return a+b;}}
- 線程同步
- 同步代碼塊
- 同步代碼塊 中的鎖對象可以是任意對象 , ?但是要使多個線程同步時,需要使用同一個鎖對象 , 才能保證線程安全。
- 同步方法
- 在方法聲明上添加 synchronized
- 同步方法的鎖對象就是this
- 靜態同步方法的鎖對象是: 類名.class
- 線程不安全示例:package com.tj.ThreadTest;/*** 電影院賣票* 本場共100張票* 多個窗口同時賣票* */
public class ThreadTest_09 {public static void main(String[] args) {ThreadDemo_09 t = new ThreadDemo_09();new Thread(t,"窗口1").start();;new Thread(t,"窗口2").start();;new Thread(t,"窗口3").start();;new Thread(t,"窗口4").start();;}
}
class ThreadDemo_09 implements Runnable{int ticket = 100;@Overridepublic void run() {//模擬賣票while(ticket>=0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在賣票,余票:"+ --ticket);}}/*** 出現了錯誤的情況 , 線程不安全* 窗口4正在賣票,余票:1窗口3正在賣票,余票:-1窗口1正在賣票,余票:0窗口2正在賣票,余票:-2* */
}
- 同步代碼塊示例:package com.tj.ThreadTest;
/*** 同步代碼塊解決 線程安全問題* */
public class ThreadTest_10 {public static void main(String[] args) {ThreadDemo_10 t = new ThreadDemo_10();new Thread(t,"窗口一").start();new Thread(t,"窗口二").start();new Thread(t,"窗口三").start();}}
class ThreadDemo_10 implements Runnable {int ticket = 10;//創建鎖對象Object lock =new Object();@Overridepublic void run() {synchronized (lock) {while(ticket>0){try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在售票,余票"+ --ticket);}}}}
- 同步方法示例:package com.tj.ThreadTest;
/*** 同步方法解決線程安全問題* * */
public class ThreadTest_11 {public static void main(String[] args) {ThreadDemo_11 t = new ThreadDemo_11();new Thread(t,"窗口一").start();new Thread(t,"窗口二").start();new Thread(t,"窗口三").start();}
}
class ThreadDemo_11 implements Runnable {int ticket = 10;//同步方法//同步方法的鎖對象就是this// 靜態同步方法的鎖對象的 : 類名.class@Overridepublic synchronized void run() {while(ticket>0){try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在售票,余票"+ --ticket);}}}
- 同步代碼塊
- 死鎖
- 同步鎖使用的弊端 : 當線程中出現 多個同步(多個鎖)時 , 如果同時嵌套了其他的同步 , 容易 引起 一種現象: 程序 無限等待 , 這種現象叫死鎖。
- 示例:package com.tj.ThreadTest;import java.util.Random;/*** 線程任務中出現多個同步時* 發生死鎖的情況* */
public class ThreadTest_12 {public static void main(String[] args) {ThreadDemo_12 t = new ThreadDemo_12();new Thread(t).start();new Thread(t).start();}
}
//定義鎖對象
class MyLock_12{public static final Object lockA = new Object();public static final Object lockB = new Object();
}
class ThreadDemo_12 implements Runnable{@Overridepublic void run() {int x = new Random().nextInt(1); // 0~1if(x%2 == 0){synchronized (MyLock_12.lockA) {System.out.println("if-lockA");synchronized (MyLock_12.lockB) {System.out.println("if - lockB");System.out.println("if 大口吃肉");}}}else{synchronized (MyLock_12.lockB) {System.out.println("if-lockB");synchronized (MyLock_12.lockA) {System.out.println("if - lockA");System.out.println("else 大口吃肉");}}}x++;}}
- Lock接口
- 常用方法 :
- Lock() : 獲取鎖對象 。
- unlock() : 釋放鎖
- 示例:package com.tj.ThreadTest;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** 使用Lock接口 * 改進賣票程序* */
public class ThreadTest_13 {public static void main(String[] args) {MyTicket_13 mt = new MyTicket_13();new Thread(mt , "窗口一").start();new Thread(mt , "窗口二").start();
// new Thread(mt , "窗口三").start();
// new Thread(mt , "窗口四").start();
// new Thread(mt , "窗口五").start();
// new Thread(mt , "窗口六").start();}
}
class MyTicket_13 implements Runnable{int ticket = 10;//創建鎖對象Lock l = new ReentrantLock();@Overridepublic void run() {//獲取鎖l.lock();while(ticket>0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在賣票,余票:" + --ticket);}//釋放鎖l.unlock();}}
- 常用方法 :
- 等待喚醒機制
- 線程之間的通訊 : 多個線程在處理同一個資源 , 但處理的動作不同。
- 等待喚醒機制 : 通過 一定的手段使各個線程能有效的利用資源 。 ?
- 常用方法:
- wait() : 等待 , 將正在執行的線程釋放其執行資格和執行權 ,并 存儲在線程池中 。
- notify() : 喚醒 , 喚醒線程池中被wait的線程 , 一次喚醒一個 , 而且是任意的 。?
- notifyAll() : 喚醒所有 , 將線程池中所有被wait的線程喚醒。?
- ?這些方法都定義在Object 中 ?, 因為:使用 時必須明確指定所屬的鎖 , 而鎖又可以是任意對象 ?, 所以能被任意對象調用的方法一定定義在Object中 。?
- 示例:package com.tj.ThreadTest;
/*** 線程中等待喚醒機制實例* 等待喚醒機制,使多個線程之間有效利用資源。* 輸入線程想Resource 中輸入name ,sex ,輸出線程從資源中輸出* 1. 檔input發現resource中沒有數據時 ,開始輸入,輸入完成后叫outPut輸出,如果已有數據 則wait* 2. 檔output發現Resource中沒有數據時 ,就wait ,有數據時喚醒輸出后叫input輸入* */
public class ThreadTest_14 {public static void main(String[] args) {//資源對象Resours_14 r = new Resours_14();//任務對象Input in = new Input(r);Output out = new Output(r);//開啟線程new Thread(in).start();new Thread(out).start();}
}
//模擬資源類
class Resours_14{private String name ;private String sex ;private boolean flag = false;public synchronized void in(String name ,String sex ){//如果有值 ,則wait狀態 ,等待輸出if(flag)try {wait();} catch (InterruptedException e) {e.printStackTrace();}//設置成員變量this.name = name;this.sex = sex;//設置之后 Resource 中有值 將flag置為trueflag = true;//喚醒outputthis.notify();}public synchronized void out(){//如果沒有值 ,則進入等待狀態 ,登臺輸入if(!flag)try {wait();} catch (InterruptedException e) {e.printStackTrace();}//將數據輸出System.out.println("姓名:" + name +"性別: "+sex);//改變標記flag = false;//喚醒input ,進行數據輸入this.notify();}
}
//輸入線程任務類
class Input implements Runnable{private Resours_14 r ;public Input(Resours_14 r) {this.r = r;}@Overridepublic void run() {int count = 0;while (true){//降低CPU壓力 try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//交替設置資源if(count == 0){r.in("小明", "男");}else{r.in("小花", "女");}//兩個數據之間相互切換count = (count +1) %2;}}
}
//輸出線程任務類
class Output implements Runnable{private Resours_14 r ;public Output(Resours_14 r) {super();this.r = r;}@Overridepublic void run() {while(true){//降低CPU壓力 try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//輸出資源內容r.out();}}}
- 補充: wait() 與 sleep() 的區別 :?
- wait() : 釋放鎖對象 , 釋放CPU使用權 , 在休眠時間內能被喚醒 。
- sleep() : 不釋放鎖對象 , 釋放CPU使用權 , 在休眠時間內不能被喚醒
九、線程安全
-
?如果有多個線程同時運行同一段代碼,程序每次運行結果和單線程運行的結果是一樣的,而且其他的變量值也與預期是一樣的,那么就是線程安全的。
-
線程的安全問題都是由全局變量和靜態變量引起的,每個線程對全局變量和局部變量只有讀操作而沒有寫操作,一般來說這個線程就是安全的;若多個線程同時執行寫操作,一般都是需要考慮線程同步,否則的話可能影響線程安全。
-
示例:
/*** 線程同步的兩種方式 1. 同步代碼塊 2. 同步方法 示例: 電影院買票 共有100張票 ,三個窗口同時買票* */ public class ThreadDemo_09 {public static void main(String[] args) {new Thread(new ThreadTest_09_01(),"窗口一").start();new Thread(new ThreadTest_09_01(),"窗口二").start();new Thread(new ThreadTest_09_01(),"窗口三").start();} }class ThreadTest_09_01 implements Runnable {private static int ticket = 100;@Overridepublic void run() {while (1 == 1) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "出票,余票"+ --ticket);}}}} 結果: 窗口一出票,余票99 窗口三出票,余票98 窗口二出票,余票99//與窗口一重復 ,線程不安全 窗口二出票,余票95 窗口三出票,余票96 窗口三出票,余票93 窗口三出票,余票92 窗口一出票,余票97 窗口二出票,余票94 窗口一出票,余票91 窗口二出票,余票90 。。。。
十、線程同步
- java中處理線程同步 的兩種方式:
- 同步代碼塊
- 同步方法
- 同步代碼塊: 在代碼塊聲明上加上synchronized ? ? 且要使用同一鎖對象才能保證線程同步。.
- 同步方法 ? : 在方法屬性上加synchronized ?。
十一、守護線程
-
守護其他線程的執行。
-
當被守護的線程結束時 , 無論守護線程是否執行完畢則隨之結束 。?
-
只要代碼中出現了線程要么是守護線程 , 要么是被守護的線程 。?
-
如果出現率多個被守護的線程 , 則以最后一個被守護的線程結束為標志而結束 。?
-
gc(垃圾回收機制)本質上就是一盒
- //設置為守護線程
t.setDaemon(true);
十二、 線程的優先級
- 線程有1~10 ? 10個優先級 。?
- 數字越大 , 優先級越高 , 理論上優先級越高的線程越容易搶到cpu執行權 , 但是相鄰的兩個優先級之間差別不大, 至少相差五個優先級才能看出效果 。?
- 如果不設置優先級 , 則默認的優先級為5 。?
- java中處理線程同步 的兩種方式:
總結
以上是生活随笔為你收集整理的Java 多线程Thread的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java IO流 、 Propertie
- 下一篇: Java初阶知识总结