Java学习笔记5-1——多线程
目錄
- 前言
- 核心概念
- 線程創(chuàng)建
- 繼承Thread類
- 實現(xiàn)Runnable接口
- 上述兩個方法小結(jié)
- 實現(xiàn)Callable接口
- 并發(fā)問題簡介
- 靜態(tài)代理模式
- 線程狀態(tài)
- 線程停止(stop)
- 線程休眠(sleep)
- 線程禮讓(yield)
- 線程強制執(zhí)行(join)
- 線程狀態(tài)觀測
- 線程優(yōu)先級
- 守護線程
- 后續(xù)內(nèi)容見《Java學(xué)習(xí)筆記5-2》
前言
Process與Thread:
- 程序(programm)是指令和數(shù)據(jù)的有序集合,其本身沒有任何運行的含義,是一個靜態(tài)的概念。
- 而進程(process)是執(zhí)行程序的一次執(zhí)行過程,它是一個動態(tài)的概念。是系統(tǒng)資源分配的單位。
- 通常在一個進程中可以包含若干個線程(thread),當(dāng)然一個進程中至少有一個線程,不然沒有存在的意義。線程是CPU調(diào)度和執(zhí)行的單位。
很多多線程是模擬出來的,真正的多線程是指有多個CPU。
核心概念
- 線程就是獨立的執(zhí)行路徑
- 在程序運行時,即使沒有自己創(chuàng)建線程,后臺也會有多個線程,如主線程,gc線程
- main()稱之為主線程,為系統(tǒng)的入口,用于執(zhí)行整個程序
- 在一個進程中,如果開辟了多個線程,線程的運行由調(diào)度器安排調(diào)度,調(diào)度器是與操作系統(tǒng)緊密相關(guān)的,先后順序是不能人為干預(yù)的。
- 對同一份資源操作時,會存在資源搶奪的問題,需要加入并發(fā)控制
- 線程會帶來額外的開銷,如cpu調(diào)度時間,并發(fā)控制開銷
- 每個線程在自己的工作內(nèi)存交互,內(nèi)存控制不當(dāng)會造成數(shù)據(jù)不一致
線程創(chuàng)建
三種創(chuàng)建線程的方式:
繼承Thread類
創(chuàng)建線程方式1:繼承Thread類,重寫run()方法,調(diào)用start開啟線程。
-
public class Thread extends Object implements Runnable
-
構(gòu)造方法:
Thread(Runnable target) 分配一個新的 Thread對象。
Thread(Runnable target, String name) 分配一個新的 Thread對象,具有指定的name作為其名稱 -
常用方法:
void start() 使此線程開始執(zhí)行; JVM調(diào)用此線程的run方法。
簡單例子:
//創(chuàng)建線程方式1:繼承Thread類,重寫run()方法,調(diào)用start開啟線程 //總結(jié):注意線程開啟不一定立即執(zhí)行,由CPU調(diào)度執(zhí)行 public class ThreadTest1 extends Thread{@Overridepublic void run() {//run方法線程的執(zhí)行體for (int i = 0; i < 200; i++) {System.out.println("我在學(xué)線程---"+i);}}public static void main(String[] args) {//main線程,主線程//創(chuàng)建一個線程對象ThreadTest1 tt = new ThreadTest1();//調(diào)用start()tt.start();for (int i = 0; i < 1000; i++) {System.out.println("(主)我在看代碼---"+i);}} }網(wǎng)圖下載例子:
//練習(xí)Thread,實現(xiàn)多線程同步下載圖片 public class TestThread2 extends Thread{private String url;//網(wǎng)絡(luò)圖片地址private String name;//保存的文件名public TestThread2(String url,String name){this.url = url;this.name = name;}// 下載圖片線程的執(zhí)行體@Overridepublic void run() {WebDownloader webDownloader = new WebDownloader();webDownloader.downloader(url,name);System.out.println("下載了文件名為"+name+"的文件");}public static void main(String[] args) {TestThread2 t1 = new TestThread2("https://www.kuangstudy.com/assert/course/c1/01.jpg","圖片1.jpg");TestThread2 t2 = new TestThread2("https://www.kuangstudy.com/assert/course/c1/02.jpg","圖片2.jpg");TestThread2 t3 = new TestThread2("https://www.kuangstudy.com/assert/course/c1/03.jpg","圖片3.jpg");t1.start();t2.start();t3.start();} } //下載器 class WebDownloader{//下載方法public void downloader(String url,String name){try {FileUtils.copyURLToFile(new URL(url),new File(name));} catch (IOException e) {e.printStackTrace();System.out.println("IO異常,Downloader出現(xiàn)問題");}} }實現(xiàn)Runnable接口
創(chuàng)建線程方式2:實現(xiàn)Runnable接口,重寫run方法,執(zhí)行線程需要丟入runnable接口實現(xiàn)類,調(diào)用start方法。
- public interface Runnable:
Runnable接口應(yīng)由任何類實現(xiàn),其實例將由線程執(zhí)行。 該類必須定義一個無參數(shù)的方法,稱為run 。
簡單例子:
//創(chuàng)建線程方式2:實現(xiàn)Runnable接口,重寫run方法,執(zhí)行線程需要丟入runnable接口實現(xiàn)類,調(diào)用start方法 public class ThreadTest3 implements Runnable{@Overridepublic void run() {//run方法線程體for (int i = 0; i < 200; i++) {System.out.println("我在學(xué)線程---"+i);}}public static void main(String[] args) {//創(chuàng)建Runnable接口的實現(xiàn)類對象ThreadTest3 tt = new ThreadTest3();//創(chuàng)建線程對象,通過線程對象來開啟我們的線程,代理new Thread(tt).start();for (int i = 0; i < 1000; i++) {System.out.println("(主)我在看代碼---"+i);}} }上述兩個方法小結(jié)
繼承Thread類:
- 子類繼承Thread類具備多線程能力
- 啟動線程:子類對象.start()
- 不建議使用。為了避免面向?qū)ο髥卫^承局限性
實現(xiàn)Runnable接口:
- 實現(xiàn)接口Runnable具有多線程能力
- 啟動線程:傳入目標對象+Thread對象.start()
- 推薦使用。能避免單繼承局限性,靈活方便,方便同一對象被多個線程使用。 例如:
代碼例子(龜兔賽跑)作為總結(jié):
//模擬龜兔賽跑 public class Race implements Runnable{//勝利者private static String winner;@Overridepublic void run() {for (int i = 0; i <= 100; i++) {//模擬兔子睡覺if (Thread.currentThread().getName().equals("兔子") && i%10==0){try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}//判斷比賽是否結(jié)束boolean flag = gameOver(i);if (flag){break;}System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");}}//判斷是否完成比賽private boolean gameOver(int steps){//判斷是否有勝利者if (winner!=null){//已經(jīng)存在勝利者return true;}{if (steps>=100){winner=Thread.currentThread().getName();System.out.println("winner is"+winner);return true;}}return false;}public static void main(String[] args) {Race race = new Race();new Thread(race,"烏龜").start();new Thread(race,"兔子").start();} }實現(xiàn)Callable接口
網(wǎng)圖下載例子:
//線程創(chuàng)建方式3:實現(xiàn)Callable接口 //Callable的好處:1.可以定義返回值;2.可以拋出異常public class CallableTest implements Callable<Boolean> {private String url;//網(wǎng)絡(luò)圖片地址private String name;//保存的文件名public CallableTest(String url,String name){this.url = url;this.name = name;}// 下載圖片線程的執(zhí)行體@Overridepublic Boolean call() {WebDownloader1 webDownloader = new WebDownloader1();webDownloader.downloader(url,name);System.out.println("下載了文件名為"+name+"的文件");return true;}public static void main(String[] args) throws ExecutionException, InterruptedException {CallableTest ct1 = new CallableTest("https://www.kuangstudy.com/assert/course/c1/01.jpg","c圖片1.jpg");CallableTest ct2 = new CallableTest("https://www.kuangstudy.com/assert/course/c1/02.jpg","c圖片2.jpg");CallableTest ct3 = new CallableTest("https://www.kuangstudy.com/assert/course/c1/03.jpg","c圖片3.jpg");// 創(chuàng)建執(zhí)行對象ExecutorService ser = Executors.newFixedThreadPool(3);// 提交執(zhí)行:Future<Boolean> r1 = ser.submit(ct1);Future<Boolean> r2 = ser.submit(ct2);Future<Boolean> r3 = ser.submit(ct3);// 獲取結(jié)果:boolean rs1 = r1.get();boolean rs2 = r2.get();boolean rs3 = r3.get();System.out.println(rs1);System.out.println(rs2);System.out.println(rs3);// 關(guān)閉服務(wù)ser.shutdownNow();} } // 下載器 class WebDownloader1{// 下載方法public void downloader(String url,String name){try {FileUtils.copyURLToFile(new URL(url),new File(name));} catch (IOException e) {e.printStackTrace();System.out.println("IO異常,Downloader出現(xiàn)問題");}} }并發(fā)問題簡介
// 多個線程同時操作同一個對象 // 買火車票的例子 public class ThreadTest4 implements Runnable{// 票數(shù)private int ticketNums = 10;@Overridepublic void run() {while (true){if (ticketNums<=0){break;}//模擬延時try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"張票");}}public static void main(String[] args) {ThreadTest4 ticket = new ThreadTest4();new Thread(ticket,"小明").start();new Thread(ticket,"老師").start();new Thread(ticket,"黃牛黨").start();} }結(jié)果:
小明拿到了第10張票
老師拿到了第8張票
黃牛黨拿到了第9張票
黃牛黨拿到了第7張票
小明拿到了第7張票
老師拿到了第7張票
黃牛黨拿到了第6張票
…
可見發(fā)生了多個人取到同一張票
靜態(tài)代理模式
靜態(tài)代理模式總結(jié):
- 真實對象和代理對象都要實現(xiàn)同一個接口
- 代理對象要代理真實角色
好處:
1.代理對象可以做很多真實對象做不了的事情
2.真實對象專注做自己的事情
線程狀態(tài)
線程停止(stop)
- 建議線程正常停止(自己停下來)。即利用次數(shù),不建議死循環(huán)
- 建議使用標志位作為終止,當(dāng)flag=false,則線程終止運行
- 不要使用stop或者destroy等過時或不建議使用的方法
基本步驟:
例子:
public class TestStop implements Runnable{// 1.設(shè)置一個標志位private boolean flag = true;@Overridepublic void run() {int i = 0;while (flag){System.out.println("run...Thread"+i++);}}// 2.設(shè)置一個公開的方法停止線程,轉(zhuǎn)換標志位public void stop(){this.flag = false;}public static void main(String[] args) {TestStop testStop = new TestStop();new Thread(testStop).start();for (int i = 0; i < 1000; i++) {System.out.println("main"+i);if (i==900) { // 主線程到i=900的時候才停止線程testStop.stop();System.out.println("線程停止了");}}} }輸出:
…
main900
run…Thread662
線程停止了
main901
…
線程休眠(sleep)
- sleep(毫秒) 指定當(dāng)前線程阻塞的毫秒數(shù)
- sleep存在異常InterruptedException
- sleep時間達到后,線程進入就緒狀態(tài)
- sleep可以模擬網(wǎng)絡(luò)延時,倒計時等
- 每一個對象都有一個鎖,sleep不會釋放鎖
例子1:在前面的了解并發(fā)問題的買票例子中可以加入延時Thread.sleep(100);,從而模擬網(wǎng)絡(luò)延遲,這樣可以放大問題的發(fā)生性。
例子2:模擬倒計時。
public class Countdown {public static void main(String[] args) {try {countdown();} catch (InterruptedException e) {e.printStackTrace();}}//模擬倒計時public static void countdown() throws InterruptedException {int num = 10;while (true){Thread.sleep(1000);System.out.println(num--);if (num<=0){break;}}} }例子3:打印當(dāng)前系統(tǒng)時間
public class SystemTime {public static void main(String[] args) throws InterruptedException {//打印當(dāng)前系統(tǒng)時間Date time = new Date(System.currentTimeMillis());//獲取當(dāng)前系統(tǒng)時間while(true){try {Thread.sleep(1000);System.out.println(new SimpleDateFormat("HH:mm:ss").format(time));time = new Date(System.currentTimeMillis());//更新當(dāng)前時間} catch (InterruptedException e) {e.printStackTrace();}}}}線程禮讓(yield)
- 禮讓線程,讓當(dāng)前正在執(zhí)行的線程暫停,但不阻塞
- 將線程從運行狀態(tài)轉(zhuǎn)為就緒狀態(tài)
- 注意:讓CPU重新調(diào)度,禮讓不一定成功!看CPU心情
輸出:(禮讓不成功)
A線程開始執(zhí)行
A線程停止執(zhí)行
B線程開始執(zhí)行
B線程停止執(zhí)行
輸出:(禮讓成功)
A線程開始執(zhí)行
B線程開始執(zhí)行
A線程停止執(zhí)行
B線程停止執(zhí)行
線程強制執(zhí)行(join)
- join合并線程,待此線程執(zhí)行完成后,再執(zhí)行其他線程,其他線程阻塞(可以想象成插隊)
線程狀態(tài)觀測
線程狀態(tài)。 線程可以處于以下狀態(tài)之一:
- NEW 尚未啟動的線程處于此狀態(tài)。
- RUNNABLE 在Java虛擬機中執(zhí)行的線程處于此狀態(tài)。
- BLOCKED 被阻塞等待監(jiān)視器鎖定的線程處于此狀態(tài)。
- WAITING 正在等待另一個線程執(zhí)行特定動作的線程處于此狀態(tài)。
- TIMED_WAITING 正在等待另一個線程執(zhí)行動作達到指定等待時間的線程處于此狀態(tài)。
- TERMINATED 已退出的線程處于此狀態(tài)。
一個線程可以在給定時間點處于一個狀態(tài)。 這些狀態(tài)是不反映任何操作系統(tǒng)線程狀態(tài)的虛擬機狀態(tài)。
// 觀察線程的狀態(tài) public class StateTest {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("干活!");});// 觀察狀態(tài)Thread.State state = thread.getState();System.out.println(state);// 觀察new了一個線程后的狀態(tài)// 觀察啟動后thread.start();// 啟動線程state = thread.getState();System.out.println(state);// 觀察線程run后的狀態(tài)while(state!=Thread.State.TERMINATED){ // 只要線程不終止,就一直輸出狀態(tài)Thread.sleep(100);state = thread.getState();// 更新線程狀態(tài)System.out.println(state);// 輸出狀態(tài)}} }輸出:
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
…
TIMED_WAITING
干活!
TERMINATED
【注意】線程start后不一定立即執(zhí)行,而是處于RUNNABLE狀態(tài),即就緒狀態(tài)。線程休眠的5秒鐘內(nèi)一直是TIMED_WAITING狀態(tài),睡醒之后就干活,干完活就線程終止
線程優(yōu)先級
-
Java提供一個線程調(diào)度器來監(jiān)控程序中啟動后進入就緒狀態(tài)的所有線程,線程調(diào)度器按照優(yōu)先級決定應(yīng)該調(diào)度哪個線程來執(zhí)行。
-
線程的優(yōu)先級用數(shù)字表示,范圍從1~10:(數(shù)字越大優(yōu)先級越高)
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
【注意】優(yōu)先級低只是意味著獲得調(diào)度的概率低。并不是優(yōu)先級低就不會被先調(diào)用,這都是取決于CPU。 -
使用以下方式改變或者獲取優(yōu)先級:
getPriority()
setPriority(int x) -
如果需要設(shè)置優(yōu)先級,先設(shè)定好優(yōu)先級再start()
守護線程
- 線程分為用戶線程和守護線程
- 虛擬機必須確保用戶線程執(zhí)行完畢
- 虛擬機不用等待守護線程執(zhí)行完畢
- 守護線程如:后臺記錄操作日志,監(jiān)控內(nèi)存,垃圾回收等
用戶線程停止之后,守護線程還在運行,因為虛擬機停止需要一段時間。
后續(xù)內(nèi)容見《Java學(xué)習(xí)筆記5-2》
總結(jié)
以上是生活随笔為你收集整理的Java学习笔记5-1——多线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 常用表的字段
- 下一篇: java软件工程师自我评价_Java工程