一、多線程
- 定義
- 進程
- 進程是程序執(zhí)行的一條路徑
- 一個系統(tǒng)級的應用執(zhí)行的就是一個進程
- 線程
- 線程在進程的內部汇在,一個進程中可以有多個線程
- 多個線程并發(fā)執(zhí)行可以提高程序的效率婿奔,可以同時完成多項工作
- 多線程節(jié)約的是執(zhí)行程序的等待時間,如果程序排列緊密宇攻,沒有等待時間,多線程就不能真正的提高效率了
- 進程
- 多線程的應該
- 迅雷下載多個任務
- 服務器同時處理多個請求
- 多線程并發(fā)和并行的區(qū)別
- 并發(fā)是指兩個任務都請求運行倡勇,而處理器只能接受一個任務逞刷,就把這兩個任務安排輪流進行寝受,由于時間間隔較短柬甥,使人感覺兩個任務都在運行
- 并行就是兩個任務同時運行,就是甲任務運行的同時辽话,乙任務也在進行(需要多核CPU)
- 如果一臺電腦我先給甲發(fā)個信息扔役,然后立刻給乙發(fā)消息帆喇,然后再跟甲聊,再跟乙聊亿胸,這就叫并發(fā)
- 比如我跟兩個網(wǎng)友聊天坯钦,左手操作一個電腦跟甲聊,同時右手用另一個電腦跟乙聊天侈玄,這就叫并行
- Java虛擬機運行流程
- java命令會啟動java虛擬機婉刀,啟動JVM,等于啟動了一個應用程序拗馒,也就是啟動了一個進程路星。該進程會自動啟動一個“主線程”,然后主線程去調用某個類中的main方法
- JVM啟動后至少會創(chuàng)建垃圾回收線程和主線程
- 思考
- 四核CPU和雙核四線程的CPU哪個效率更高诱桂?(四核CPU)
二洋丐、Java中多線程的實現(xiàn)方式一
-
步驟
- 繼承Thread方式
- 定義類繼承Thread
- 重寫run方法
- 把新線程要做的事寫在run方法中
- 創(chuàng)建線程對象
- 調用start()方法開啟新線程,內部會自動執(zhí)行run()方法
-
演示
public class MyThread extends Thread{ @Override- public void run() { while(true){ System.out.println("我是子線程"); } } } public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); }
-
原理解析
- 創(chuàng)建多線程肯定要跟系統(tǒng)平臺的底層打交道挥等,我們程序猿根本就不知道如何去做友绝,所以,我們僅僅是提供運行代碼肝劲,至于如何創(chuàng)建多線程全靠java實現(xiàn)
- 繼承Thread的形式迁客,每個Thread的子類對象只能創(chuàng)建一個線程
三郭宝、Java中多線程的實現(xiàn)方式二
-
步驟
- 定義類實現(xiàn)Runnable接口
- 實現(xiàn)run方法
- 把新線程要做的事寫在run方法中
- 創(chuàng)建自定義的Runnable的子類對象
- 創(chuàng)建Thread對象,傳入Runnable
- 調用start()開啟新線程掷漱,內部會自動調取Runnable中的run()方法
-
演示
public class MyRunnable implements Runnable{ @Override public void run() { while(true){ System.out.println("我是子線程"); } } } public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); Thread thread2 = new Thread(myRunnable); thread.start(); thread2.start(); }
-
原理
- Thread類中定義了一個Runnable類型的成員變量target用來接收Runnable的子類對象
- 當調用Thread對象的start()方法的時候粘室,方法內首先會判斷target是否為null,如果不為null卜范,就調用Runnable子類對象的run方法
- 多個Thread對象可以共享一個Runnable子類對象
四衔统、多線程兩種方式的區(qū)別
- 查看源碼的區(qū)別
- 繼承Thread:由于子類重寫了Thread類的run(),當調用start()時海雪,直接找子類的run方法
- 實現(xiàn)Runnable:構造函數(shù)中傳入了Runnable的引用锦爵,成員變量記住了它,start()調用run()方法時內部判斷成員變量Runnable的引用是否為空奥裸,不為空险掀,編譯時看的是Runnable的run(),運行時執(zhí)行的是子類的run方法
- 繼承Thread
- 好處是:可以直接使用Thread類中的方法湾宙,代碼簡單
- 弊端是:如果已經(jīng)有了父類樟氢,就不能使用這種方法了
- 實現(xiàn)Runnable接口
- 好處是:即使自己定義的線程類有了父類也沒有關系,因為有了父類也可以實現(xiàn)接口创倔,而且接口是可以多實現(xiàn)的嗡害,多個線程可以非常方便的使用同一個對象的成員變量
- 弊端是:不能直接使用Thread中的方法,需要先獲取到線程對象后畦攘,才能得到Thread的方法霸妹,代碼復雜
- 思考
- 當我們調用start()方法之后,是否就意味著線程立即運行呢知押?(否)
- CPU的執(zhí)行權(時間片):如果當前線程被CPU執(zhí)行的時候叹螟,我們稱當前線程獲取了CPU的執(zhí)行權
- 調用start方法之后,意味著當前線程準備好被執(zhí)行了
五台盯、匿名內部類實現(xiàn)多線程的兩種方式
-
繼承Thread類
public static void main(String[] args) { new Thread(){ public void run(){ while(true){ System.out.println("我是子線程"); } } }.start(); }
-
實現(xiàn)Runnable接口
public static void main(String[] args) { //創(chuàng)建Thread對象,提供Runnable匿名內部類對象 new Thread(new Runnable(){ public void run(){ while(true){ System.out.println("我是子線程"); } } }).start(); }
六罢绽、多線程中獲取名字和設置名字
-
獲取名字
- 通過getName()方法獲取線程對象的名字
- 此方法只適用于Thread的形式
- Runnable的形式必須通過獲取線程對象來獲取名字
-
演示
public class MyThread extends Thread{ @Override public void run() { System.out.println(this.getName()); } }
-
設置名字
-
通過構造方法傳入String類型的名字
public static void main(String[] args) { new Thread("線程一"){ public void run(){ System.out.println(this.getName()); } }.start(); }
-
通過setName方法可以設置線程對象的名字
public static void main(String[] args) { Thread thread1 = new Thread(){ public void run(){ System.out.println(this.getName()); } }; thread1.setName("線程一"); thread1.start(); }
-
七、獲取當前線程的對象名
-
定義
- 我們可以在多線程運行代碼中獲取代表當前執(zhí)行線程的對象静盅,并對其進行操作
-
演示
-
通過獲取對象的形式在Runnable運行代碼中查看當前線程的名稱
public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { //獲取線程的名稱 System.out.println(Thread.currentThread().getName()); } }).start(); }
-
通過獲取線程對象的形式設置線程的名稱
public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { Thread.currentThread().setName("線程二");//設置線程的名稱 System.out.println(Thread.currentThread().getName()); } }).start(); }
-
通過Thread的構造方法設置線程的名稱
public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } //通過構造方法設置線程的名稱 },"線程二").start(); }
-
八良价、休眠線程(sleep)
-
定義
- 在線程的執(zhí)行代碼中調用Thread.sleep()方法,就可以將線程休眠若干毫秒
- 休眠結束的線程進入可運行狀態(tài)(就緒狀態(tài))而不是直接運行
-
演示
public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { long time1 = System.currentTimeMillis(); System.out.println(time1); try { Thread.sleep(10000);//設置線程等待10000毫秒 } catch (InterruptedException e) { e.printStackTrace(); } long time2 = System.currentTimeMillis(); System.out.println(time2); } //通過構造方法設置線程的名稱 }).start(); }
-
測試題(龜兔賽跑)
- 需求: 烏龜和兔子賽跑總賽程100m, 兔子的速度是10m/s, 烏龜?shù)乃俣仁?m/s.烏龜和兔子都是每跑完10米輸出一次結果, 當兔子跑到70米的時候休息2s ,編程模擬比賽過程
九蒿叠、守護線程(setDaemon)
-
定義
- 圍繞著其他非守護線程運行明垢,該線程不會單獨運行,當其他非守護線程都執(zhí)行結束后市咽,自動退出
- 調用Thread的setDaemon()方法設置一個線程為守護線程
-
應用場景
- 使用飛秋聊天時痊银,我們打開了多個聊天窗口,當我們關閉主程序時施绎,其他所有的聊天窗口都會隨之關閉
-
演示
public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("線程一運行中"); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("守護線程運行.........."); } } }); t2.setDaemon(true); t1.start(); t2.start(); }
十溯革、加入線程(join)
-
定義
- 當前線程暫停贞绳,等待指定的線程執(zhí)行結束后,當前線程再繼續(xù)
-
常用方法
- join()優(yōu)先執(zhí)行指定線程
- join(毫秒) 優(yōu)先執(zhí)行指定線程若干毫秒
-
演示
public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("線程一運行中"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("線程二運行中.........."); try { t1.join(); //讓線程一先執(zhí)行 t1.join(100);//讓線程一先執(zhí)行100毫秒 } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }); //只有調用了加入線程的線程才會停止運行,其他線程不受影響 Thread t3 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("線程三運行中.........."); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }); t1.start(); t2.start(); t3.start(); }
-
注意事項
- 只有調用了加入線程(也就是join方法)的線程才會停止運行致稀,其他線程不受影響
十一冈闭、禮讓線程(了解)(yield)
-
定義
- 僅僅讓出當前線程的一次執(zhí)行權
- 基本看不到效果
-
演示
public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("線程一運行中"); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("線程二運行中.........."); //讓出當前線程的執(zhí)行權 Thread.yield(); } } }); t1.start(); t2.start(); }
十二、設置線程的優(yōu)先級(setPriority)
-
定義
- 每個線程都有優(yōu)先級豺裆,默認都是5拒秘,范圍是1-10,1表示優(yōu)先級最低
- 優(yōu)先級高的線程在爭奪CPU的執(zhí)行權上有一定的優(yōu)勢臭猜,但是表示絕對的
-
演示
public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("線程一運行中"); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("線程二運行中.........."); } } }); t1.setPriority(1); t2.setPriority(10); //線程二的優(yōu)先級高 t1.start(); t2.start(); }
十三、多線程安全問題
-
定義
- 多個線程操作同一個數(shù)據(jù)時押蚤,因為線程執(zhí)行的隨機性蔑歌,就有可能出現(xiàn)線程安全問題
- 使用同步可以解決這個問題,把操作數(shù)據(jù)的代碼進行同步揽碘,同一時間只能有一個線程操作數(shù)據(jù)次屠,這樣就可以保證數(shù)據(jù)的安全性
-
線程安全問題演示
static int num =10; public static void main(String[] args) { Thread t1 = new Thread(){ public void run(){ while(true){ if (num>0) { System.out.println(getName()+":"+num--); }else{ break; } } } }; Thread t2 = new Thread(){ public void run(){ while(true){ if (num>0) { System.out.println(getName()+":"+num--); }else{ break; } } } }; t1.start(); t2.start(); }
-
解決安全問題
-
使用synchronized關鍵字鎖定部分代碼
static int num = 10; public static void main(String[] args) { Thread t1 = new Thread(){ public void run(){ while(true){ //加上鎖 synchronized (Class.class) { if (num>0) { System.out.println(getName()+":"+num--); }else{ break; } } } } }; Thread t2 = new Thread(){ public void run(){ while(true){ //加上統(tǒng)一把鎖 synchronized (Class.class) { if (num>0) { System.out.println(getName()+":"+num--); }else{ break; } } } } }; t1.start(); t2.start(); }
-
注意事項
- 使用同一把鎖的代碼才能實現(xiàn)同步
- 沒有獲取到鎖的線程即使得到了CPU的執(zhí)行權,也不能執(zhí)行
- 盡量減少鎖的范圍雳刺,避免效率低下
- 鎖可以加在任意類的代碼中或方法上
-
測試題
- 火車站總共有100張票, 四個窗口同時賣票, 當票賣光了之后,提示"沒票了...",編程模擬場景
-
十四劫灶、死鎖
-
定義
- 使用同步的多個線程同時持有對方運行時所需要的資源
- 多線程同步時,多個同步代碼塊嵌套掖桦,很容易就會出現(xiàn)死鎖
- 鎖嵌套的越多本昏,越容易造成死鎖的情況
-
演示
- 線程一需要先獲取左邊的筷子然后獲取右邊的筷子才能吃飯
- 線程二需要先獲取右邊的筷子然后獲取左邊的筷子才能吃飯
- 當線程一持有左邊的筷子,線程二持有右邊的筷子時,相互等待對方釋放鎖,但又同時持有對方釋放鎖的條件,互不相讓,就會造成無限等待
private static String s1 = "筷子左"; private static String s2 = "筷子右"; public static void main(String[] args) { new Thread() { public void run() { while(true) { synchronized(s1) { System.out.println(getName() + "...拿到" + s1 + "等待" + s2); synchronized(s2) { System.out.println(getName() + "...拿到" + s2 + "開吃"); } } } } }.start(); new Thread() { public void run() { while(true) { synchronized(s2) { System.out.println(getName() + "...拿到" + s2 + "等待" + s1); synchronized(s1) { System.out.println(getName() + "...拿到" + s1 + "開吃"); } } } } }.start(); }
總結
- 多線程
- 節(jié)省等待時間
- 平均運行時間
- 創(chuàng)建多線程的兩種方式
- 繼承Thread
- 實現(xiàn)Runnable
- 常用方法
- Thread.currentThread()
- Thread.sleep(毫秒)
- thread.setDeamon()
- thread.join()
- Thread.yield()
- thread.setPriority(1-10)
- 多線程的安全問題
- 多個線程同時爭搶資源,一定會出現(xiàn)問題
- 同步鎖 synchronized
- 死鎖
- 使用同步的多個線程同時持有對方運行時所需要的資源