多執行緒+JAVA學習筆記-DAY24
阿新 • • 發佈:2018-11-04
24.01_多執行緒(多執行緒的引入)(瞭解)
- 1.什麼是執行緒
- 執行緒是程式執行的一條路徑, 一個程序中可以包含多條執行緒
- 多執行緒併發執行可以提高程式的效率, 可以同時完成多項工作
- 2.多執行緒的應用場景
- 紅蜘蛛同時共享螢幕給多個電腦
- 迅雷開啟多條執行緒一起下載
- QQ同時和多個人一起視訊
- 伺服器同時處理多個客戶端請求
24.02_多執行緒(多執行緒並行和併發的區別)(瞭解)
- 並行就是兩個任務同時執行,就是甲任務進行的同時,乙任務也在進行。(需要多核CPU)
- 併發是指兩個任務都請求執行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行,由於時間間隔較短,使人感覺兩個任務都在執行。
- 比如我跟兩個網友聊天,左手操作一個電腦跟甲聊,同時右手用另一臺電腦跟乙聊天,這就叫並行。
- 如果用一臺電腦我先給甲發個訊息,然後立刻再給乙發訊息,然後再跟甲聊,再跟乙聊。這就叫併發。
24.03_多執行緒(Java程式執行原理和JVM的啟動是多執行緒的嗎)(瞭解)
A:Java程式執行原理
- Java命令會啟動java虛擬機器,啟動JVM,等於啟動了一個應用程式,也就是啟動了一個程序。該程序會自動啟動一個 “主執行緒” ,然後主執行緒去呼叫某個類的 main 方法。
B:JVM的啟動是多執行緒的嗎
JVM啟動至少啟動了垃圾回收執行緒和主執行緒,所以是多執行緒的。
public class Demo1_Thread { /** * @param args * 證明jvm是多執行緒的 */ public static void main(String[] args) { for(int i = 0; i < 100000; i++) { new Demo(); } for(int i = 0; i < 10000; i++) { System.out.println("我是主執行緒的執行程式碼"); } } } class Demo { @Override public void finalize() { System.out.println("垃圾被清掃了"); } }
24.04_多執行緒(多執行緒程式實現的方式1)(掌握)
1.繼承Thread
- 定義類繼承Thread
- 重寫run方法
- 把新執行緒要做的事寫在run方法中
- 建立執行緒物件
- 開啟新執行緒, 內部會自動執行run方法
public class Demo2_Thread { /** * @param args */ public static void main(String[] args) { MyThread mt = new MyThread(); //4,建立自定義類的物件 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,將要執行的程式碼,寫在run方法中 System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); } } }
24.05_多執行緒(多執行緒程式實現的方式2)(掌握)
2.實現Runnable
- 定義類實現Runnable介面
- 實現run方法
- 把新執行緒要做的事寫在run方法中
- 建立自定義的Runnable的子類物件
- 建立Thread物件, 傳入Runnable
呼叫start()開啟新執行緒, 內部會自動呼叫Runnable的run()方法
public class Demo3_Runnable { /** * @param args */ public static void main(String[] args) { MyRunnable mr = new MyRunnable(); //4,建立自定義類物件 //Runnable target = new MyRunnable(); Thread t = new Thread(mr); //5,將其當作引數傳遞給Thread的建構函式 t.start(); //6,開啟執行緒 for(int i = 0; i < 3000; i++) { System.out.println("bb"); } } } class MyRunnable implements Runnable { //1,自定義類實現Runnable介面 @Override public void run() { //2,重寫run方法 for(int i = 0; i < 3000; i++) { //3,將要執行的程式碼,寫在run方法中 System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); } } }
24.06_多執行緒(實現Runnable的原理)(瞭解)
- 檢視原始碼
- 1,看Thread類的建構函式,傳遞了Runnable介面的引用
- 2,通過init()方法找到傳遞的target給成員變數的target賦值
- 3,檢視run方法,發現run方法中有判斷,如果target不為null就會呼叫Runnable介面子類物件的run方法
24.07_多執行緒(兩種方式的區別)(掌握)
檢視原始碼的區別:
- a.繼承Thread : 由於子類重寫了Thread類的run(), 當呼叫start()時, 直接找子類的run()方法
- b.實現Runnable : 建構函式中傳入了Runnable的引用, 成員變數記住了它, start()呼叫run()方法時內部判斷成員變數Runnable的引用是否為空, 不為空編譯時看的是Runnable的run(),執行時執行的是子類的run()方法
繼承Thread
- 好處是:可以直接使用Thread類中的方法,程式碼簡單
- 弊端是:如果已經有了父類,就不能用這種方法
- 實現Runnable介面
- 好處是:即使自己定義的執行緒類有了父類也沒關係,因為有了父類也可以實現介面,而且介面是可以多實現的
- 弊端是:不能直接使用Thread中的方法需要先獲取到執行緒物件後,才能得到Thread的方法,程式碼複雜
24.08_多執行緒(匿名內部類實現執行緒的兩種方式)(掌握)
繼承Thread類
new Thread() { //1,new 類(){}繼承這個類 public void run() { //2,重寫run方法 for(int i = 0; i < 3000; i++) { //3,將要執行的程式碼,寫在run方法中 System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa"); } } }.start();
實現Runnable介面
new Thread(new Runnable(){ //1,new 介面(){}實現這個介面 public void run() { //2,重寫run方法 for(int i = 0; i < 3000; i++) { //3,將要執行的程式碼,寫在run方法中 System.out.println("bb"); } } }).start();
24.09_多執行緒(獲取名字和設定名字)(掌握)
- 1.獲取名字
- 通過getName()方法獲取執行緒物件的名字
2.設定名字
- 通過建構函式可以傳入String型別的名字
new Thread("xxx") { public void run() { for(int i = 0; i < 1000; i++) { System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa"); } } }.start(); new Thread("yyy") { public void run() { for(int i = 0; i < 1000; i++) { System.out.println(this.getName() + "....bb"); } } }.start();
- 通過setName(String)方法可以設定執行緒物件的名字
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_多執行緒(獲取當前執行緒的物件)(掌握)
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("我是主執行緒"); //獲取主函式執行緒的引用,並改名字 System.out.println(Thread.currentThread().getName()); //獲取主函式執行緒的引用,並獲取名字
24.11_多執行緒(休眠執行緒)(掌握)
Thread.sleep(毫秒,納秒), 控制當前執行緒休眠若干毫秒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_多執行緒(守護執行緒)(掌握)
setDaemon(), 設定一個執行緒為守護執行緒, 該執行緒不會單獨執行, 當其他非守護執行緒都執行結束後, 自動退出
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設定為守護執行緒 t1.start(); t2.start();
24.13_多執行緒(加入執行緒)(掌握)
- join(), 當前執行緒暫停, 等待指定的執行緒執行結束後, 當前執行緒再繼續
join(int), 可以等待指定的毫秒之後繼續
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); //加入,有固定的時間,過了固定時間,繼續交替執行 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getName() + "...bb"); } } }; t1.start(); t2.start();
24.14_多執行緒(禮讓執行緒)(瞭解)
- yield讓出cpu
24.15_多執行緒(設定執行緒的優先順序)(瞭解)
- setPriority()設定執行緒的優先順序(數值越大,執行的次數先對而言多些)
24.16_多執行緒(同步程式碼塊)(掌握)
- 1.什麼情況下需要同步
- 當多執行緒併發, 有多段程式碼同時執行時, 我們希望某一段程式碼執行的過程中CPU不要切換到其他執行緒工作. 這時就需要同步.
- 如果兩段程式碼是同步的, 那麼同一時間只能執行一段, 在一段程式碼沒執行結束之前, 不會執行另外一段程式碼.
2.同步程式碼塊
- 使用synchronized關鍵字加上一個鎖物件來定義一段程式碼, 這就叫同步程式碼塊
多個同步程式碼塊如果使用相同的鎖物件, 那麼他們就是同步的
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關鍵字修飾一個方法, 該方法中所有的程式碼都是同步的
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"); } } /* * 非靜態同步函式的鎖是:this * 靜態的同步函式的鎖是:位元組碼物件 */ public static synchronized void print2() { System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); } }
24.18_多執行緒(執行緒安全問題)(掌握)
- 多執行緒併發操作同一資料時, 就有可能出現執行緒安全問題
使用同步技術可以解決這種問題, 把操作資料的程式碼進行同步, 不要多個執行緒一起操作
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_多執行緒(火車站賣票的例子用實現Runnable介面)(掌握)
public class Demo4_Ticket {
/**
* @param args
* 火車站賣票的例子用實現Runnable介面
*/
public static void main(String[] args) {
MyTicket mt = new MyTicket();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
/*Thread t1 = new Thread(mt); //多次啟動一個執行緒是非法的
t1.start();
t1.start();
t1.start();
t1.start();*/
}
}
class MyTicket implements Runnable {
private int tickets = 100;
@Override
public void run() {
while(true) {
synchronized(this) {
if(tickets <= 0) {
break;
}
try {
Thread.sleep(10); //執行緒1睡,執行緒2睡,執行緒3睡,執行緒4睡
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "...這是第" + tickets-- + "號票");
}
}
}
}
24.20_多執行緒(死鎖)(瞭解)
多執行緒同步的時候, 如果同步程式碼巢狀, 使用相同鎖, 就有可能出現死鎖
儘量不要巢狀使用
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是執行緒不安全的