一、多線程基本了解
1、多線程基本知識
1.進程:進入到內存中執行的應用程序
2.線程:內存和CPU之間開通的通道->進程中的一個執行單元
3.線程作用:負責當前進程中程序的運行.一個進程中至少有一個線程,一個進程還可以有多個線程,這樣的應用程序就稱之為多線程程序
4.簡單理解:進程中的一個功能就需要一條線程去執行
2、并發和并行
1.并行:并行:在同一個時刻,有多個指令在多個CPU上(指多核即幾核幾線程)執行(好比是多個人做不同的事兒)
比如:多個廚師在炒多個菜
2.并發:并發:在同一個時刻,有多個指令在單個CPU上(交替)執行
比如:一個廚師在炒多個菜
細節:1.之前CPU是單核,但是在執行多個程序的時候好像是在同時執行,原因是CPU在多個線程之間做高速切換2.現在咱們的CPU都是多核多線程的了,比如2核4線程,那么CPU可以同時運行4個線程,此時不用切換,但是如果多了,CPU就要切換了,所以現在CPU在執行程序的時候并發和并行都存在
3、CPU調度
1.分時調度:讓所有的線程輪流獲取CPU使用權,并且平均分配每個線程占用CPU的時間片
2.搶占式調度:多個線程搶占CPU使用權,哪個線程優先級越高,先搶到CPU使用權的幾率就大,但是不是說每次先搶到CPU使用權的都是優先級高的線程,只是優先級高的線程先搶到CPU使用權的幾率會大一些 -> java代碼(搶占式調度)
4、主線程介紹
主線程:為main方法服務的線程
二、創建線程的方式(重點)
1、第一種方式_extends Thread
1.定義一個類,繼承Thread類
2.重寫Thread類中的run方法,設置線程任務(該線程要做的事兒)
3.創建自定義線程類對象
4.調用Thread類中的start方法start()開啟線程,jvm會自動執行run方法
定義線程對象
public class Day15Thread extends Thread {@Overridepublic void run(){for (int i = 0; i < 5; i++) {System.out.println("MyThread執行了:"+i);}}}
定義主線程
public class Day15ThreadText {public static void main(String[] args) {Day15Thread t1 = new Day15Thread();//t.run();t1.start();//開啟線程,jvm自動執行run方法for (int i = 0; i < 5; i++) {System.out.println("main...執行了"+i);}}
}運行結果:
MyThread執行了:0
MyThread執行了:1
main...執行了0
main...執行了1
main...執行了2
MyThread執行了:2
MyThread執行了:3
MyThread執行了:4
main...執行了3
main...執行了4
注意:如果直接調用run方法,并不代表將線程開啟,僅僅是簡單的調用方法 > > 只有調用start方法,線程才會真正開啟
2、多線程在內存中的運行原理
首先會內存會開啟一個棧供main方法執行,此時main方法中的start()方法被調用,調用后內存會重新開啟一個棧供新的線程運行
3、Thread中常用方法
void start() -> 開啟線程,jvm自動調用run方法
void run() -> 設置線程任務,這個run方法是Thread重寫的接口Runnable中的run方法
String getName() -> 獲取線程名字
void setName(String name) -> 給線程設置名字
static Thread currentThread() -> 獲取正在執行的線程對象(此方法在哪個線程中使用,獲取的就是哪個線程對象)
static void sleep(long millis)->線程睡眠,超時后自動醒來繼續執行,傳遞的是毫秒值
定義自定義線程
public class Day15Thread extends Thread {@Overridepublic void run(){for (int i = 0; i < 5; i++) {try {Thread.sleep(1000L);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName()+"執行了:"+i);}}
}
定義主線程
public class Day15ThreadText {public static void main(String[] args) throws InterruptedException {Day15Thread t1 = new Day15Thread();//t.run();t1.setName("劉大膽");t1.start();//開啟線程,jvm自動執行run方法for (int i = 0; i < 5; i++) {Thread.sleep(1000L);System.out.println("主線程名稱為"+Thread.currentThread().getName()+i);}}
}
> 1.父類方法拋異常了,子類重寫之后可拋可不拋 > > 父類方法沒有拋異常,子類重寫之后不要拋 > > 2.重寫了run方法之后,由于Thread中的run方法沒有throws,那么我們重寫run之后就不能throws,只能try
4、Thread其他方法
void setPriority(int newPriority) -> 設置線程優先級,優先級越高的線程,搶到CPU使用權的幾率越大,但是不是每次都先搶到int getPriority() -> 獲取線程優先級void setDaemon(boolean on) -> 設置為守護線程,當非守護線程執行完畢,守護線程就要結束,但是守護線程也不是立馬結束,當非守護線程結束之后,系統會告訴守護線程人家結束了,你也結束吧,在告知的過程中,守護線程會執行,只不過執行到半路就結束了static void yield() -> 禮讓線程,讓當前線程讓出CPU使用權void join() -> 插入線程或者叫做插隊線程
4.1 線程優先級
public class Day15ThreadText {public static void main(String[] args) throws InterruptedException {Day15Thread t1 = new Day15Thread();//t.run();t1.setName("劉大膽");Day15Thread t2 = new Day15Thread();t2.setName("超級劉大膽");t1.setPriority(Thread.MIN_PRIORITY); //設置優先級t2.setPriority(Thread.MAX_PRIORITY);t1.start();//開啟線程,jvm自動執行run方法t2.start();System.out.println(t1.getPriority()); //獲取優先級System.out.println(t2.getPriority());for (int i = 0; i < 5; i++) {System.out.println("主線程名稱為"+Thread.currentThread().getName()+i);}}
}
輸出結果:
1
10
超級劉大膽執行了:0
主線程名稱為main0
超級劉大膽執行了:1
主線程名稱為main1
超級劉大膽執行了:2
主線程名稱為main2
超級劉大膽執行了:3
主線程名稱為main3
超級劉大膽執行了:4
主線程名稱為main4
劉大膽執行了:0
劉大膽執行了:1
劉大膽執行了:2
劉大膽執行了:3
劉大膽執行了:4
4.2 守護線程
定義自定義線程1
public class Day15Thread1 extends Thread {@Overridepublic void run(){for (int i = 0; i < 10; i++) {System.out.println(getName()+"執行了:"+i);}}}
定義自定義線程2
public class Day15Thread2 extends Thread{@Overridepublic void run(){for (int i = 0; i <100; i++) {System.out.println(getName()+"執行了:"+i);}}
}
定義主線程
public class Day15ThreadText {public static void main(String[] args) throws InterruptedException {Day15Thread1 t1 = new Day15Thread1();//t.run();t1.setName("劉大膽");Day15Thread2 t2 = new Day15Thread2();t2.setName("超級劉大膽");//設置t2為守護線程t2.setDaemon( true);t1.start();//開啟線程,jvm自動執行run方法t2.start();}
}
輸出結果:超級劉大膽執行了:0
劉大膽執行了:0
超級劉大膽執行了:1
劉大膽執行了:1
超級劉大膽執行了:2
劉大膽執行了:2
超級劉大膽執行了:3
劉大膽執行了:3
超級劉大膽執行了:4
劉大膽執行了:4
超級劉大膽執行了:5
劉大膽執行了:5
超級劉大膽執行了:6
劉大膽執行了:6
超級劉大膽執行了:7
劉大膽執行了:7
超級劉大膽執行了:8
劉大膽執行了:8
超級劉大膽執行了:9
劉大膽執行了:9
超級劉大膽執行了:10
超級劉大膽執行了:11
超級劉大膽執行了:12
超級劉大膽執行了:13
超級劉大膽執行了:14
超級劉大膽執行了:15
超級劉大膽執行了:16
超級劉大膽執行了:17
超級劉大膽執行了:18
超級劉大膽執行了:19
由此可見 當非守護線程結束后 守護線程不會直接結束 而是執行一段時間后再結束
4.3 禮讓線程
場景說明:如果兩個線程一起執行,可能會執行一會兒線程A,再執行一會線程B,或者可能線程A執行完畢了,線程B再執行那么我們能不能讓兩個線程盡可能的平衡一點 -> 盡量讓兩個線程交替執行
注意:只是盡可能的平衡,不是絕對的你來我往,有可能線程A線程執行,然后禮讓了,但是回頭A又搶到CPU使用權了
public class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i<= 10; i++) {System.out.println(Thread.currentThread().getName()+"...執行了"+i);//禮讓線程Thread.yield();}}
}
public class Demo01Thread {public static void main(String[] args){MyThread t1 = new MyThread();//設置線程名字t1.setName("趙四");MyThread t2 = new MyThread();t2.setName("廣坤");t1.start();//開啟線程,jvm自動執行run方法t2.start();//開啟線程,jvm自動執行run方法}
}
4.4 插入線程
public class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i <= 10; i++) {System.out.println(Thread.currentThread().getName()+"...執行了"+i);}}
}
public class Demo01Thread {public static void main(String[] args) throws InterruptedException {MyThread t = new MyThread();//設置線程名字t.setName("趙四");t.start();//開啟線程,jvm自動執行run方法/*join(),插入線程將t1插入到當前線程前面,現在只有兩條線程,一個t1,一個主線程我們想將t1插入到主線程前面,主線程就是當前線程*/t.join();for (int i = 0; i <= 10; i++) {System.out.println(Thread.currentThread().getName()+"...執行了"+i);}}
}
5、第二種方式_實現Runnable接口
1.創建一個自定義類,實現Runnable接口
2.重寫run方法,設置線程任務
3.創建自定義類對象
4.利用Thread類的構造,創建Thread對象 ->Thread(Runnable r)Thread t1 = new Thread(自定義類對象)
5.調用start方法,開啟線程
定義實現類
public class Day15Runnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+"執行了:"+i);}}
}
定義主線程
,public class DayRunnableThread {public static void main(String[] args) {Day15Runnable t1 = new Day15Runnable();/*Thread(Runnable r)Thread(Runnable r,String name) -> 創建Thread對象的同時給線程設置名字*/Thread t = new Thread(t1);t.start();for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+"執行了:"+i);}}
}
6、兩種實現多線程的方式區別
1.繼承Thread: 由于繼承只能進行單繼承,會有很大的局限性
2.實現Runnable: 可以繼承一個父類的同時實現一個或者多個接口,沒有繼承的局限性
7、匿名內部類創建多線程
package com.code.day15;public class Day15RunnableNI {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+"執行了:"+i);}}}).start();
//Thread(Runnable r ,String name)new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+"執行了:"+i);}}},"超級劉大膽").start();}
}
三、線程安全
1.線程不安全原因
出現線程不安全的原因:多個線程同時訪問同一個資源
線程不安全代碼
public class Day15Safe implements Runnable {int ticket = 100;@Overridepublic void run() {while (true) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "...買了第" + ticket + "張票");ticket--;}}}
}
package com.code.day15;public class Day15SafeTest {public static void main(String[] args) {Day15Safe t = new Day15Safe();Thread t1 = new Thread(t,"趙四");Thread t2 = new Thread(t,"廣坤");Thread t3 = new Thread(t,"劉能");t1.start();t2.start();t3.start();}
}
2.解決線程安全問題的第一種方式(使用同步代碼塊)
1.格式:synchronized(鎖對象){可能出現線程不安全的代碼}2.鎖對象:a.任意對象b.想要實現線程安全,多個線程之間用的鎖對象就必須是同一個鎖對象3.線程執行,搶到鎖進入同步代碼塊執行,其他線程等待排隊,需要等著執行的線程出了同步代碼塊,將鎖釋放,其他等待線程才能搶鎖,進入到同步代碼塊中執行
定義自定義線程及鎖
package com.code.day15;public class Day15Safe implements Runnable {int ticket = 100;Object obj = new Object();@Overridepublic void run() {while (true) {try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (obj){if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "...買了第" + ticket + "張票");ticket--;}}}}
}
測試
package com.code.day15;public class Day15SafeTest {public static void main(String[] args) {Day15Safe t = new Day15Safe();Thread t1 = new Thread(t,"趙四");Thread t2 = new Thread(t,"廣坤");Thread t3 = new Thread(t,"劉能");t1.start();t2.start();t3.start();}
}
輸出結果:
趙四...買了第100張票
劉能...買了第99張票
廣坤...買了第98張票
劉能...買了第97張票
趙四...買了第96張票
廣坤...買了第95張票
廣坤...買了第94張票
劉能...買了第93張票
趙四...買了第92張票 ......
3.解決線程安全問題的第二種方式:同步方法
3.1普通同步方法_非靜態
定義鎖
package com.code.day15;public class Day15Safe implements Runnable {int ticket = 100;Object obj = new Object();@Overridepublic void run() {while (true) {try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}method();}}public synchronized void method() {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "...買了第" + ticket + "張票");ticket--;}//三個對象共享一個鎖
// public void method() {
// synchronized (this) {
// if (ticket > 0) {
// System.out.println(Thread.currentThread().getName() + "...買了第" + ticket + "張票");
// ticket--;
// }
// }
// }}
}
3.2靜態同步方法
1.格式:修飾符 static synchronized 返回值類型 方法名(形參){方法體return 結果}2.默認鎖:當前類.class -> class對象,現在先記住,class對象在 反射會講
package com.code.day15;public class Day15Safe implements Runnable {static int ticket = 100;Object obj = new Object();@Overridepublic void run() {while (true) {try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}method();}}public static synchronized void method() {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "...買了第" + ticket + "張票");ticket--;
// }
// public void method() {
// synchronized (Day15SafeTest.class) {
// if (ticket > 0) {
// System.out.println(Thread.currentThread().getName() + "...買了第" + ticket + "張票");
// ticket--;
// }}}
}
兩種方法的差距主要在成員變量與方法的static
關于StringBuilder線程不安全與StringBuffer線程安全是因為StringBuffer中放置了安全鎖
四、死鎖
1、死鎖介紹(鎖嵌套就可能導致死鎖)
指的是兩個或者兩個以上的線程在執行的過程中由于競爭同步鎖而產生的一種阻塞現象;如果沒有外力的作用,他們將無法繼續執行下去,這種情況稱之為死鎖
根據上圖所示:線程1正在持有鎖1,但是線程1必須再拿到鎖2,才能繼續執行
而線程2正在持有鎖2,但是線程2需要再拿到鎖1,才能繼續執行
此時兩個線程處于互相等待的狀態,就是死鎖,在程序中的死鎖將出現在同步代碼塊的嵌套中
2、死鎖的分析
3、代碼實現
定義鎖A
public class LockA {public static LockA lockA = new LockA();
}
定義鎖B
public class LockB {public static LockB lockB = new LockB();
}
自定義線程
package com.code.day15;public class DieLock implements Runnable {private boolean flag;public DieLock(boolean flag) {this.flag = flag;}@Overridepublic void run() {if (flag) {synchronized (LockA.lockA) {System.out.println("A線程開始執行");synchronized (LockB.lockB) {System.out.println("B線程開始執行");}}} else {synchronized (LockB.lockB) {System.out.println("B線程開始執行");synchronized (LockA.lockA) {System.out.println("A線程開始執行");}}}}
}
測試
package com.code.day15;public class Day15LockTest {public static void main(String[] args) {DieLock dieLock1 = new DieLock( true);DieLock dieLock2 = new DieLock( false);Thread thread1 = new Thread(dieLock1);Thread thread2 = new Thread(dieLock2);thread1.start();thread2.start();}
}
A線程開始執行
B線程開始執行由此可見以上程序出現了死鎖問題
em.out.println(“B線程開始執行”);
}
}
} else {
synchronized (LockB.lockB) {
System.out.println(“B線程開始執行”);
synchronized (LockA.lockA) {
System.out.println(“A線程開始執行”);
}
}
}
}
}
**測試**```java
package com.code.day15;public class Day15LockTest {public static void main(String[] args) {DieLock dieLock1 = new DieLock( true);DieLock dieLock2 = new DieLock( false);Thread thread1 = new Thread(dieLock1);Thread thread2 = new Thread(dieLock2);thread1.start();thread2.start();}
}
A線程開始執行
B線程開始執行由此可見以上程序出現了死鎖問題