24.01_多線程(多線程的引入)(了解)
- 1.什么是線程
- 線程是程序執(zhí)行的一條路徑, 一個進(jìn)程中可以包含多條線程
- 多線程并發(fā)執(zhí)行可以提高程序的效率, 可以同時完成多項工作
- 2.多線程的應(yīng)用場景
- 紅蜘蛛同時共享屏幕給多個電腦
- 迅雷開啟多條線程一起下載
- QQ同時和多個人一起視頻
- 服務(wù)器同時處理多個客戶端請求
24.02_多線程(多線程并行和并發(fā)的區(qū)別)(了解)
- 并行就是兩個任務(wù)同時運行,就是甲任務(wù)進(jìn)行的同時,乙任務(wù)也在進(jìn)行咸作。(需要多核CPU)
- 并發(fā)是指兩個任務(wù)都請求運行锨阿,而處理器只能按受一個任務(wù),就把這兩個任務(wù)安排輪流進(jìn)行记罚,由于時間間隔較短墅诡,使人感覺兩個任務(wù)都在運行。
- 比如我跟兩個網(wǎng)友聊天桐智,左手操作一個電腦跟甲聊末早,同時右手用另一臺電腦跟乙聊天,這就叫并行说庭。
- 如果用一臺電腦我先給甲發(fā)個消息然磷,然后立刻再給乙發(fā)消息,然后再跟甲聊刊驴,再跟乙聊姿搜。這就叫并發(fā)。
24.03_多線程(Java程序運行原理和JVM的啟動是多線程的嗎)(了解)
- A:Java程序運行原理
- Java命令會啟動java虛擬機(jī)捆憎,啟動JVM舅柜,等于啟動了一個應(yīng)用程序,也就是啟動了一個進(jìn)程躲惰。該進(jìn)程會自動啟動一個 “主線程” 致份,然后主線程去調(diào)用某個類的 main 方法。
- B:JVM的啟動是多線程的嗎
- JVM啟動至少啟動了垃圾回收線程和主線程礁扮,所以是多線程的知举。
24.04_多線程(多線程程序?qū)崿F(xiàn)的方式1)(掌握)
- 1.繼承Thread
定義類繼承Thread
重寫run方法
把新線程要做的事寫在run方法中
創(chuàng)建線程對象
-
開啟新線程, 內(nèi)部會自動執(zhí)行run方法
public class Demo2_Thread { /** * @param args */ public static void main(String[] args) { MyThread mt = new MyThread(); //4,創(chuàng)建自定義類的對象 mt.start(); //5,開啟線程 for(int i = 0; i < 3000; i++) { System.out.println("bb"); } } } class MyThread extends Thread { //1,定義類繼承Thread public void run() { //2,重寫run方法 for(int i = 0; i < 3000; i++) { //3,將要執(zhí)行的代碼,寫在run方法中 System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); } } }
24.05_多線程(多線程程序?qū)崿F(xiàn)的方式2)(掌握)
- 2.實現(xiàn)Runnable
定義類實現(xiàn)Runnable接口
實現(xiàn)run方法
把新線程要做的事寫在run方法中
創(chuàng)建自定義的Runnable的子類對象
創(chuàng)建Thread對象, 傳入Runnable
-
調(diào)用start()開啟新線程, 內(nèi)部會自動調(diào)用Runnable的run()方法
public class Demo3_Runnable { /** * @param args */ public static void main(String[] args) { MyRunnable mr = new MyRunnable(); //4,創(chuàng)建自定義類對象 //Runnable target = new MyRunnable(); Thread t = new Thread(mr); //5,將其當(dāng)作參數(shù)傳遞給Thread的構(gòu)造函數(shù) t.start(); //6,開啟線程 for(int i = 0; i < 3000; i++) { System.out.println("bb"); } } } class MyRunnable implements Runnable { //1,自定義類實現(xiàn)Runnable接口 @Override public void run() { //2,重寫run方法 for(int i = 0; i < 3000; i++) { //3,將要執(zhí)行的代碼,寫在run方法中 System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); } } }
24.06_多線程(實現(xiàn)Runnable的原理)(了解)
- 查看源碼
- 1,看Thread類的構(gòu)造函數(shù),傳遞了Runnable接口的引用
- 2,通過init()方法找到傳遞的target給成員變量的target賦值
- 3,查看run方法,發(fā)現(xiàn)run方法中有判斷,如果target不為null就會調(diào)用Runnable接口子類對象的run方法
24.07_多線程(兩種方式的區(qū)別)(掌握)
- 查看源碼的區(qū)別:
- a.繼承Thread : 由于子類重寫了Thread類的run(), 當(dāng)調(diào)用start()時, 直接找子類的run()方法
- b.實現(xiàn)Runnable : 構(gòu)造函數(shù)中傳入了Runnable的引用, 成員變量記住了它, start()調(diào)用run()方法時內(nèi)部判斷成員變量Runnable的引用是否為空, 不為空編譯時看的是Runnable的run(),運行時執(zhí)行的是子類的run()方法
- 繼承Thread
- 好處是:可以直接使用Thread類中的方法,代碼簡單
- 弊端是:如果已經(jīng)有了父類,就不能用這種方法
- 實現(xiàn)Runnable接口
- 好處是:即使自己定義的線程類有了父類也沒關(guān)系,因為有了父類也可以實現(xiàn)接口,而且接口是可以多實現(xiàn)的
- 弊端是:不能直接使用Thread中的方法需要先獲取到線程對象后,才能得到Thread的方法,代碼復(fù)雜
24.08_多線程(匿名內(nèi)部類實現(xiàn)線程的兩種方式)(掌握)
-
繼承Thread類
new Thread() { //1,new 類(){}繼承這個類 public void run() { //2,重寫run方法 for(int i = 0; i < 3000; i++) { //3,將要執(zhí)行的代碼,寫在run方法中 System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); } } }.start();
-
實現(xiàn)Runnable接口
new Thread(new Runnable(){ //1,new 接口(){}實現(xiàn)這個接口 public void run() { //2,重寫run方法 for(int i = 0; i < 3000; i++) { //3,將要執(zhí)行的代碼,寫在run方法中 System.out.println("bb"); } } }).start();
24.09_多線程(獲取名字和設(shè)置名字)(掌握)
- 1.獲取名字
- 通過getName()方法獲取線程對象的名字
- 2.設(shè)置名字
- 通過構(gòu)造函數(shù)可以傳入String類型的名字
new Thread("xxx") {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
}
}
}.start();
- 通過構(gòu)造函數(shù)可以傳入String類型的名字
new Thread("yyy") {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "....bb");
}
}
}.start();
-
通過setName(String)方法可以設(shè)置線程對象的名字
Thread t1 = new Thread() { public void run() { for(int i = 0; i < 1000; i++) { System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa"); } } }; Thread t2 = new Thread() { public void run() { for(int i = 0; i < 1000; i++) { System.out.println(this.getName() + "....bb"); } } }; t1.setName("芙蓉姐姐"); t2.setName("鳳姐"); t1.start(); t2.start();
24.10_多線程(獲取當(dāng)前線程的對象)(掌握)
-
Thread.currentThread(), 主線程也可以獲取
new Thread(new Runnable() { public void run() { for(int i = 0; i < 1000; i++) { System.out.println(Thread.currentThread().getName() + "...aaaaaaaaaaaaaaaaaaaaa"); } } }).start(); new Thread(new Runnable() { public void run() { for(int i = 0; i < 1000; i++) { System.out.println(Thread.currentThread().getName() + "...bb"); } } }).start(); Thread.currentThread().setName("我是主線程"); //獲取主函數(shù)線程的引用,并改名字 System.out.println(Thread.currentThread().getName()); //獲取主函數(shù)線程的引用,并獲取名字
24.11_多線程(休眠線程)(掌握)
-
Thread.sleep(毫秒,納秒), 控制當(dāng)前線程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000納秒 1000000000
new Thread() { public void run() { for(int i = 0; i < 10; i++) { System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread() { public void run() { for(int i = 0; i < 10; i++) { System.out.println(getName() + "...bb"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start();
24.12_多線程(守護(hù)線程)(掌握)
-
setDaemon(), 設(shè)置一個線程為守護(hù)線程, 該線程不會單獨執(zhí)行, 當(dāng)其他非守護(hù)線程都執(zhí)行結(jié)束后, 自動退出
Thread t1 = new Thread() { public void run() { for(int i = 0; i < 50; i++) { System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread t2 = new Thread() { public void run() { for(int i = 0; i < 5; i++) { System.out.println(getName() + "...bb"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }; t1.setDaemon(true); //將t1設(shè)置為守護(hù)線程 t1.start(); t2.start();
24.13_多線程(加入線程)(掌握)
join(), 當(dāng)前線程暫停, 等待指定的線程執(zhí)行結(jié)束后, 當(dāng)前線程再繼續(xù)
-
join(int), 可以等待指定的毫秒之后繼續(xù)
final Thread t1 = new Thread() { public void run() { for(int i = 0; i < 50; i++) { System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread t2 = new Thread() { public void run() { for(int i = 0; i < 50; i++) { if(i == 2) { try { //t1.join(); //插隊,加入 t1.join(30); //加入,有固定的時間,過了固定時間,繼續(xù)交替執(zhí)行 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getName() + "...bb"); } } }; t1.start(); t2.start();
24.14_多線程(禮讓線程)(了解)
- yield讓出cpu
24.15_多線程(設(shè)置線程的優(yōu)先級)(了解)
- setPriority()設(shè)置線程的優(yōu)先級
24.16_多線程(同步代碼塊)(掌握)
- 1.什么情況下需要同步
- 當(dāng)多線程并發(fā), 有多段代碼同時執(zhí)行時, 我們希望某一段代碼執(zhí)行的過程中CPU不要切換到其他線程工作. 這時就需要同步.
- 如果兩段代碼是同步的, 那么同一時間只能執(zhí)行一段, 在一段代碼沒執(zhí)行結(jié)束之前, 不會執(zhí)行另外一段代碼.
- 2.同步代碼塊
使用synchronized關(guān)鍵字加上一個鎖對象來定義一段代碼, 這就叫同步代碼塊
-
多個同步代碼塊如果使用相同的鎖對象, 那么他們就是同步的
class Printer { Demo d = new Demo(); public static void print1() { synchronized(d){ //鎖對象可以是任意對象,但是被鎖的代碼需要保證是同一把鎖,不能用匿名對象 System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); } } public static void print2() { synchronized(d){ System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); } } }
24.17_多線程(同步方法)(掌握)
-
使用synchronized關(guān)鍵字修飾一個方法, 該方法中所有的代碼都是同步的
class Printer { public static void print1() { synchronized(Printer.class){ //鎖對象可以是任意對象,但是被鎖的代碼需要保證是同一把鎖,不能用匿名對象 System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); } } /* * 非靜態(tài)同步函數(shù)的鎖是:this * 靜態(tài)的同步函數(shù)的鎖是:字節(jié)碼對象 */ public static synchronized void print2() { System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); } }
24.18_多線程(線程安全問題)(掌握)
多線程并發(fā)操作同一數(shù)據(jù)時, 就有可能出現(xiàn)線程安全問題
-
使用同步技術(shù)可以解決這種問題, 把操作數(shù)據(jù)的代碼進(jìn)行同步, 不要多個線程一起操作
public class Demo2_Synchronized { /** * @param args * 需求:鐵路售票,一共100張,通過四個窗口賣完. */ public static void main(String[] args) { TicketsSeller t1 = new TicketsSeller(); TicketsSeller t2 = new TicketsSeller(); TicketsSeller t3 = new TicketsSeller(); TicketsSeller t4 = new TicketsSeller(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t4.setName("窗口4"); t1.start(); t2.start(); t3.start(); t4.start(); } } class TicketsSeller extends Thread { private static int tickets = 100; static Object obj = new Object(); public TicketsSeller() { super(); } public TicketsSeller(String name) { super(name); } public void run() { while(true) { synchronized(obj) { if(tickets <= 0) break; try { Thread.sleep(10);//線程1睡,線程2睡,線程3睡,線程4睡 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "...這是第" + tickets-- + "號票"); } } } }
24.19_多線程(火車站賣票的例子用實現(xiàn)Runnable接口)(掌握)
24.20_多線程(死鎖)(了解)
- 多線程同步的時候, 如果同步代碼嵌套, 使用相同鎖, 就有可能出現(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(); }
-
24.21_多線程(以前的線程安全的類回顧)(掌握)
- A:回顧以前說過的線程安全問題
- 看源碼:Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
- Vector是線程安全的,ArrayList是線程不安全的
- StringBuffer是線程安全的,StringBuilder是線程不安全的
- Hashtable是線程安全的,HashMap是線程不安全的
24.22_多線程(總結(jié))