24 多執行緒(上)
阿新 • • 發佈:2018-12-03
24.01_多執行緒(多執行緒的引入)(瞭解)
- 1.什麼是執行緒
- 執行緒是程式執行的一條路徑, 一個程序中可以包含多條執行緒
- 多執行緒併發執行可以提高程式的效率, 可以同時完成多項工作
- 2.多執行緒的應用場景
- 紅蜘蛛同時共享螢幕給多個電腦
- 迅雷開啟多條執行緒一起下載
- QQ同時和多個人一起視訊
- 伺服器同時處理多個客戶端請求
24.02_多執行緒(多執行緒並行和併發的區別)(瞭解)
- 並行就是兩個任務同時執行,就是甲任務進行的同時,乙任務也在進行。(需要多核CPU)
- 併發是指兩個任務都請求執行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行,由於時間間隔較短,使人感覺兩個任務都在執行。
- 比如我跟兩個網友聊天,左手操作一個電腦跟甲聊,同時右手用另一臺電腦跟乙聊天,這就叫並行。
- 如果用一臺電腦我先給甲發個訊息,然後立刻再給乙發訊息,然後再跟甲聊,再跟乙聊。這就叫併發。
24.03_多執行緒(Java程式執行原理和JVM的啟動是多執行緒的嗎)(瞭解)
-
A:Java程式執行原理
- Java命令會啟動java虛擬機器,啟動JVM,等於啟動了一個應用程式,也就是啟動了一個程序。該程序會自動啟動一個 “主執行緒” ,然後主執行緒去呼叫某個類的 main 方法。
-
B:JVM的啟動是多執行緒的嗎
- JVM啟動至少啟動了垃圾回收執行緒和主執行緒,所以是多執行緒的。
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"); //執行緒有一個預設的名字,Thread-0 } } }.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("芙蓉姐姐"); //通過setName(String)方法可以設定執行緒物件的名字 t2.setName("鳳姐"); t1.start(); t2.start();
24.10_多執行緒(獲取當前執行緒的物件)(掌握)
- Thread.currentThread(),獲取當前執行緒,主執行緒也可以獲取
public static void main(String[] args) {
new Thread() {
public void run() {
System.out.println(getName() + "....aaaaaa");
}
}.start();
new Thread(new Runnable() {
public void run() {
//Thread.currentThread()獲取當前正在執行的執行緒
System.out.println(Thread.currentThread().getName() + "...bb");
}
}).start();
Thread.currentThread().setName("我是主執行緒");
System.out.println(Thread.currentThread().getName());//獲取主執行緒的名字
}
24.11_多執行緒(休眠執行緒)(掌握)
sleep()是Thread類中的靜態方法,可以直接類名.呼叫
-
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設定為守護執行緒,當t2執行完後t1就自動結束 t1.start(); t2.start();
-
24.13_多執行緒(加入執行緒)(掌握)
- join(), 當前執行緒暫停, 等待指定的執行緒執行結束後, 當前執行緒再繼續
- join(int), 可以等待指定的毫秒之後繼續
-
final Thread t1 = new Thread() { //匿名內部類使用該方法中的區域性變數時要使用final進行修飾,t1.join()處在第 //二個匿名內部類中,且t1是區域性變數,所以要用final修飾 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執行緒插隊,加入 t1.join(30); //插隊30毫秒,有固定的時間,過了固定時間,插隊完後繼續交替執行 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getName() + "...bb"); } } }; t1.start(); t2.start();
-
24.14_多執行緒(禮讓執行緒)(瞭解)
- Thread類中的靜態方法yield(),讓出cpu
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
class MyThread extends Thread {
public void run() {
for(int i = 1; i <= 1000; i++) {
if(i % 10 == 0) {
Thread.yield(); //讓出CPU
}
System.out.println(getName() + "..." + i);
}
}
24.15_多執行緒(設定執行緒的優先順序)(瞭解)
- setPriority()設定執行緒的優先順序,作用不明顯
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println(getName() + "...aaaaaaaaa" );
}
}
};
Thread t2 = new Thread(){
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println(getName() + "...bb" );
}
}
};
//t1.setPriority(10); 設定最大優先順序
//t2.setPriority(1);
t1.setPriority(Thread.MIN_PRIORITY); //設定最小的執行緒優先順序
t2.setPriority(Thread.MAX_PRIORITY); //設定最大的執行緒優先順序
t1.start();
t2.start();
}
24.16_多執行緒(同步程式碼塊)(掌握)
- 1.什麼情況下需要同步
- 當多執行緒併發, 有多段程式碼同時執行時, 我們希望某一段程式碼執行的過程中CPU不要切換到其他執行緒工作. 這時就需要同步.
- 如果兩段程式碼是同步的, 那麼同一時間只能執行一段, 在一段程式碼沒執行結束之前, 不會執行另外一段程式碼.
- 2.同步程式碼塊
- 使用synchronized關鍵字加上一個鎖物件來定義一段程式碼, 這就叫同步程式碼塊
- 多個同步程式碼塊如果使用相同的鎖物件, 那麼他們就是同步的
public static void main(String[] args) {
final Printer p = new Printer();
new Thread() {
public void run() {
while(true) {
p.print1();
}
}
}.start();
new Thread() {
public void run() {
while(true) {
p.print2();
}
}
}.start();
}
}
class Printer {
Demo d = new Demo();
public void print1() {
//synchronized(new Demo()) { //同步程式碼塊,鎖機制,鎖物件可以是任意的
synchronized(d) {
System.out.print("黑");
System.out.print("馬");
System.out.print("程");
System.out.print("序");
System.out.print("員");
System.out.print("\r\n");
}
}
public void print2() {
//synchronized(new Demo()) { //鎖物件不能用匿名物件,因為匿名物件不是同一個物件
synchronized(d) {
System.out.print("傳");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
}
}
class Demo{}
24.17_多執行緒(同步方法)(掌握)
- 使用synchronized關鍵字修飾一個方法, 該方法中所有的程式碼都是同步的
class Printer2 {
//非靜態的同步方法的鎖物件是神馬?
//答:非靜態的同步方法的鎖物件是this
//靜態的同步方法的鎖物件是什麼?
//是該類的位元組碼物件
public static synchronized void print1() { //同步方法只需要在方法上加synchronized關鍵字即可
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(Printer2.class) { //該類的位元組碼物件
System.out.print("傳");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
}
}
}
24.18_多執行緒(執行緒安全問題)(掌握)
- 多執行緒併發操作同一資料時, 就有可能出現執行緒安全問題
- 使用同步技術可以解決這種問題, 把操作資料的程式碼進行同步, 不要多個執行緒一起操作
/**
* 需求:鐵路售票,一共100張,通過四個視窗賣完.
*/
public static void main(String[] args) {
new Ticket().start();
new Ticket().start();//四個視窗
new Ticket().start();
new Ticket().start();
}
class Ticket extends Thread {
private static int ticket = 100; //靜態變數,讓這幾個執行緒共享同一個變數
//private static Object obj = new Object(); //如果用引用資料型別成員變數當作鎖物件,必須是靜態的
public void run() {
while(true) {
synchronized(Ticket.class) { //多執行緒共同修改同一變數ticket,應使用同步
if(ticket <= 0) {
break;
}
try {
Thread.sleep(10); //執行緒1睡,執行緒2睡,執行緒3睡,執行緒4睡
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "...這是第" + ticket-- + "號票");
}
}
}
}
24.19_多執行緒(火車站賣票的例子用實現Runnable介面)(掌握)
/**
* @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) { //只例項化了一次物件,四條執行緒用的是同一物件,所以可以用本類物件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是執行緒不安全的
- Collections.synchroinzed(xxx)可以將執行緒不安全的變成執行緒安全的