java中的多執行緒 // 基礎
java 中的多執行緒
簡介
程序 : 指正在執行的程式,並具有一定的獨立能力,即 當硬碟中的程式進入到記憶體中執行時,就變成了一個程序
執行緒 : 是程序中的一個執行單元,負責當前程式的執行。執行緒就是CPU通向程式的路徑
一個程序中只有一個執行緒,單執行緒程式
一個程序中是可以有多個執行緒的,這個應用程式是多執行緒程式
程式的執行分類
分時排程
所有執行緒輪流使用CPU 的使用權,平均分配每個執行緒佔用CPU 的時間
搶佔式排程
優先讓優先順序高的執行緒使用CPU,如果執行緒的優先順序相同,那麼就會隨機選擇一個執行緒(執行緒的隨機性)。
java 使用的為搶佔式排程
搶佔式排程簡介:
現在的作業系統都支援多程序併發執行,比如:一邊用office ,一邊使用QQ,一邊看著視訊 等等,
看著好像這些程式都在同一時刻執行,實際上是 CPU(中央處理器)使用搶佔式排程模式在多個執行緒間進行著高速的切換。
對於CPU的一個核而言,某個時刻只能執行一個執行緒,而CPU 在多個執行緒之間切換的速度相對我們而言感覺要快,看上去就是在同一時刻執行。
注意:
多執行緒並不能提高程式的執行速度,但能夠提高程式的執行效率,讓CPU 的使用效率更高。
多執行緒的由來
jvm啟動後,必然有一個執行執行緒(路徑)從main方法開始的,一直執行到main方法結束,這個執行緒在java中稱之為主執行緒(main執行緒)。
若主執行緒遇到迴圈,並迴圈次數較多,則導致程式在指定的位置停留時間過長,無法馬上執行下面的程式,則需要等待迴圈結束後才能夠執行程式碼。效率慢。
主執行緒負責執行其中的一個迴圈,由另一個執行緒執行另一個迴圈,最終實現多部分程式碼同時執行。多執行緒之間互不影響。
多執行緒的建立方式
1、繼承 Thread 類
建立一個類,繼承 Thread 類,並重寫Thread 類的 run 方法
建立物件,呼叫 run 方法 ,就相當於執行其他執行緒的 main 方法。
步驟:
1、自定義一個類,繼承Thread 類
2、重寫Thread 類中的 run 方法 ,設定執行緒任務
3、建立自定義類的例項
4、例項化 Thread 類,並傳入自定義的類
4、呼叫start ,開啟程序。
示例:
1 1、自定義類 2 // 建立一個類,繼承Thread 3 public class Thread_01 extends Thread{ 4 // 重寫run 方法,在run方法中定義執行緒任務 5 public void run(){ 6 System.out.println(Thread.currentThread().getName()); 7 } 8 } 9 2、main方法 10 public class ThreadDemo { 11 public static void main(String[] args) { 12 // 建立執行緒物件 t1 13 Thread_01 t1 = new Thread_01(); 14 // 為了啟動Thread_01 這個執行緒,需要例項化Thread,並傳入自己的Thread_01例項 15 Thread thread = new Thread(t1); 16 // 通知CPU 要啟動執行緒 17 thread.start(); 18 System.out.println(Thread.currentThread().getName()); 19 } 20 }多執行緒繼承Thread 示例
2、實現 Runnable 介面
建立一個類,實現 Runnable 介面,重寫 run 方法
步驟:
1、自定義一個類,實現 Runnable 介面
2、重寫run 方法,在run方法中設定執行緒任務
3、在main 方法中,建立自定義類的例項化
4、例項化Thread 類,並傳入自定義類的例項化
5、呼叫start 方法,開啟程序
1 1、自定義類,實現runnable 介面 2 // 自定義類,實現Runnable 介面 3 public class Runnable_01 implements Runnable{ 4 // 重寫run 方法,在run方法中設定執行緒任務 5 @Override 6 public void run() { 7 System.out.println(Thread.currentThread().getName()); 8 } 9 10 } 11 12 2、在main方法中,呼叫 13 public class ThreadDemo { 14 public static void main(String[] args) { 15 // 建立執行緒物件 t1 16 Runnable_01 t1 = new Runnable_01(); 17 // 為了啟動Runnable_01 這個執行緒,需要例項化Thread,並傳入自己的Runnable_01例項 18 Thread thread = new Thread(t1); 19 // 通知CPU 要啟動執行緒 20 thread.start(); 21 System.out.println(Thread.currentThread().getName()); 22 } 23 }多執行緒實現 Runnable 介面
注意:
呼叫 start 方法,開啟新執行緒。若沒有呼叫start 方法,只調用run 方法,只是呼叫了一個方法而已,並沒有開啟新執行緒
一個執行緒只能呼叫一次start 方法,若執行緒執行結束後,不能再呼叫。
常用API
-
void
start() 使該執行緒開始執行;Java 虛擬機器呼叫該執行緒的
run
方法。static
Thread
currentThread() 返回對當前正在執行的執行緒物件的引用string getName() 返回該執行緒的名稱
long getId() 返回該執行緒的識別符號
int getPriority() 返回執行緒的優先順序
Thread.State getState() 返回該執行緒的狀態
void interrupt() 中斷執行緒
void setName(String name) 改變執行緒名稱,使之與引數
name
相同void setPriority(int newPriority)更改執行緒的優先順序
static void
sleep(long millis) 在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行)static void
yield() 暫停當前正在執行的執行緒物件,並執行其他執行緒
1 public class Demo { 2 public static void main(String[] args) { 3 // 呼叫currentThread().getName(),獲取當前執行緒的名稱 4 System.out.println(Thread.currentThread().getName() + "123"); 5 // 呼叫currentThread().getId(),獲取當前執行緒的識別符號 6 System.out.println(Thread.currentThread().getId()); 7 // 呼叫currentThread().getPriority(),獲取當前執行緒的優先順序 8 System.out.println(Thread.currentThread().getPriority()); 9 // 呼叫currentThread().setName() 給當前執行緒設定新名稱 10 Thread.currentThread().setName("執行緒新名稱"); 11 // 呼叫currentThread().getName(),獲取當前執行緒的名稱 12 System.out.println(Thread.currentThread().getName() + "123"); 13 /** 14 * 列印結果 :main123 15 * 1 16 * 5 17 * 執行緒新名稱123 18 */ 19 20 } 21 }執行緒API 使用示例
執行緒安全
若多執行緒呼叫全域性變數時,會出現執行緒安全問題。
即:使用java 模擬視窗賣票時,一個視窗就是一個執行緒,若同時賣票,可能會出現幾個視窗同時賣一張票,或者賣出不存在的票(就剩一張票時,兩個視窗同時賣出)
所以,使用多執行緒時,要注意執行緒安全問題,解決執行緒安全問題有三種方式,
方式一:同步程式碼塊
同步程式碼塊:就是在方法塊宣告上加上 synchronized
synchronized (鎖物件) { 可能會產生執行緒安全問題的程式碼 }
注意:
同步程式碼塊中的鎖物件可以是任意的物件;但多個執行緒時,要使用同一個鎖物件才能夠保證執行緒安全。
物件可以是this, 哪個物件呼叫,方法中的this就是哪個物件
示例:
1 /* 2 * 開啟3個執行緒,同時賣100張票 3 */ 4 public class Demo01PayTicket { 5 public static void main(String[] args) { 6 //建立介面的實現類物件 7 RunnableImpl r = new RunnableImpl(); 8 //建立執行緒物件 9 Thread t0 = new Thread(r); 10 Thread t1 = new Thread(r); 11 Thread t2 = new Thread(r); 12 //開啟多執行緒 13 t0.start(); 14 t1.start(); 15 t2.start(); 16 } 17 } 18 19 /* 20 * 發現程式出現了安全問題:賣出了重複的票和不存在的票 21 * 22 * 多執行緒安全問題的第一種解決方案:使用同步程式碼塊 23 * 24 * 注意: 25 * 程式碼塊中傳遞的鎖物件必須保證唯一,多個執行緒使用的是同一個鎖物件 26 * 鎖物件可以是任意的物件 27 * 28 */ 29 public class RunnableImpl implements Runnable{ 30 31 //定義一個共享的票源 32 private int ticket = 100; 33 //建立一個鎖物件 34 Object obj = new Object(); 35 36 @Override 37 public void run() { 38 //讓賣票重複執行 39 while(true){ 40 //同步程式碼塊 41 synchronized (obj) { 42 //判斷是否還有票 43 if(ticket>0){ 44 45 //提高安全問題出現的概率,增加一個sleep 46 try { 47 Thread.sleep(10); 48 } catch (InterruptedException e) { 49 e.printStackTrace(); 50 } 51 52 //進行賣票 53 System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票"); 54 ticket--; 55 } 56 } 57 } 58 } 59 }多執行緒 同步程式碼塊
方式二:同步方法
1、同步方法:在方法宣告上加上 synchronized
public synchronized void method(){
可能會產生執行緒安全問題的程式碼
}
同步方法中的鎖物件是 this,哪個物件呼叫,方法中的this就是哪個物件
2、靜態同步方法:在方法宣告上加上 static synchronized
public static synchronized void method(){
可能會產生執行緒安全問題的程式碼
}
靜態同步方法中的鎖物件不是物件,是本類的 .class 檔案
示例:
1 /* 2 * 開啟3個執行緒,同時賣100張票 3 */ 4 public class Demo01PayTicket { 5 public static void main(String[] args) { 6 //建立介面的實現類物件 7 RunnableImpl r = new RunnableImpl(); 8 //建立執行緒物件 9 Thread t0 = new Thread(r); 10 Thread t1 = new Thread(r); 11 Thread t2 = new Thread(r); 12 //開啟多執行緒 13 t0.start(); 14 t1.start(); 15 t2.start(); 16 } 17 } 18 19 /* 20 * 發現程式出現了安全問題:賣出了重複的票和不存在的票 21 * 22 * 多執行緒安全問題的第二種解決方案:使用同步方法 23 * 24 * 實現步驟: 25 * 1.把訪問了共享資料的程式碼提取出來放在一個方法中 26 * 2.在方法上新增一個synchronized修飾符 27 * 28 * 格式: 29 * 修飾符 synchronized 返回值型別 方法名(引數){ 30 * 訪問了共享資料的程式碼; 31 * } 32 * 33 * 把選中的程式碼提取到方法中快捷鍵:alt+shift+m 34 * 35 */ 36 public class RunnableImpl implements Runnable{ 37 38 //定義一個共享的票源 39 private static int ticket = 100; 40 41 @Override 42 public void run() { 43 //讓賣票重複執行 44 while(true){ 45 payTicketStatic(); 46 } 47 48 } 49 50 /* 51 * 靜態的同步方法,鎖物件不是this 52 * 靜態優先於非靜態載入到記憶體中,this是建立物件之後才有的 53 * 鎖物件是本類的class屬性(反射-->class檔案物件) 54 */ 55 public static synchronized void payTicketStatic() { 56 synchronized (RunnableImpl.class) { 57 //判斷是否還有票 58 if(ticket>0){ 59 //提高安全問題出現的概率,增加一個sleep 60 try { 61 Thread.sleep(10); 62 } catch (InterruptedException e) { 63 e.printStackTrace(); 64 } 65 //進行賣票 66 System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票"); 67 ticket--; 68 } 69 } 70 } 71 72 /* 73 * 定義一個賣票的方法 74 * 使用synchronized修飾 75 * 使用鎖物件把方法鎖住 76 * 這個鎖物件是誰? 77 * 建立的實現類物件new RunnableImpl(); 78 * 也就是this,哪個物件呼叫的方法,方法中的this就是哪個物件 79 */ 80 public synchronized void payTicket() { 81 //System.out.println(this);//[email protected] 82 synchronized (this) { 83 //判斷是否還有票 84 if(ticket>0){ 85 //提高安全問題出現的概率,增加一個sleep 86 try { 87 Thread.sleep(10); 88 } catch (InterruptedException e) { 89 e.printStackTrace(); 90 } 91 92 //進行賣票 93 System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票"); 94 ticket--; 95 } 96 } 97 } 98 }多執行緒 同步方法 synchronized
方式三:使用lock 鎖
Lock 是介面, ReentrantLock 是Lock 的實現類
API
呼叫
1、建立ReentrantLock 物件
2、在可能產生安全問題程式碼前呼叫 lock() 方法,獲得鎖
3、呼叫unlock()方法,解鎖
ReentrantLock rl = new ReentrantLock();
//獲得鎖
rl.LOCK
可能會產生執行緒安全問題的程式碼
Rl.unlock
示例:
1 /* 2 * 開啟3個執行緒,同時賣100張票 3 */ 4 public class Demo01PayTicket { 5 public static void main(String[] args) { 6 //建立介面的實現類物件 7 RunnableImpl r = new RunnableImpl(); 8 //建立執行緒物件 9 Thread t0 = new Thread(r); 10 Thread t1 = new Thread(r); 11 Thread t2 = new Thread(r); 12 //開啟多執行緒 13 t0.start(); 14 t1.start(); 15 t2.start(); 16 } 17 } 18 19 /* 20 * 發現程式出現了安全問題:賣出了重複的票和不存在的票 21 * 22 * 多執行緒安全問題的第三種解決方案:使用Lock鎖 23 * java.util.concurrent.locks.Lock介面 24 * Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。 25 * JDK1.5之後出現的新特性 26 * 27 * 實現步驟: 28 * 1.在成員位置建立一個ReentrantLock物件 29 * 2.在訪問了共享資料的程式碼前,呼叫lock方法,獲取鎖物件 30 * 3.在訪問了共享資料的程式碼後,呼叫unlock方法,釋放鎖物件 31 * 32 */ 33 public class RunnableImpl implements Runnable{ 34 35 //定義一個共享的票源 36 private int ticket = 100; 37 //1.在成員位置建立一個ReentrantLock物件 38 Lock l = new ReentrantLock(); 39 40 @Override 41 public void run() { 42 //讓賣票重複執行 43 while(true){ 44 //2.在訪問了共享資料的程式碼前,呼叫lock方法,獲取鎖物件 45 l.lock(); 46 try { 47 //可能會出現安全問題的程式碼 48 //判斷是否還有票 49 if(ticket>0){ 50 //提高安全問題出現的概率,增加一個sleep 51 Thread.sleep(10); 52 //進行賣票 53 System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票"); 54 ticket--; 55 } 56 57 } catch (Exception e) { 58 //異常的處理邏輯 59 System.out.println(e); 60 } finally { 61 //一定會執行的程式碼,資源釋放 62 //3.在訪問了共享資料的程式碼後,呼叫unlock方法,釋放鎖物件 63 l.unlock();//無論是否異常,都會釋放掉鎖物件 64 } 65 } 66 67 } 68 }多執行緒 lock 鎖 示例
多執行緒執行緒圖
執行緒的五種狀態:
新建狀態-->執行狀態-->死亡(結束)狀態
阻塞狀態
凍結狀態(休眠/無限等待)
新建 :剛創建出來的執行緒,即 new Thread();
阻塞 :沒有搶到CPU,在等待CPU呼叫
執行 :呼叫run 方法,在執行狀態
死亡 :方法結束,呼叫完成
休眠 :呼叫sleep() 方法,進入休眠狀態
sleep 是Thread 的一個函式
sleep 指佔用CPU 不工作,其他執行緒無法進入。即:sleep不會讓出系統資源;
無限等待 : 呼叫wait() 方法,未被喚醒
wait 是object 的一個函式,需要呼叫notify() 來喚醒
wait 指不佔用CPU 不工作,其他執行緒可以進入。即:wait是進入執行緒等待池中等待,讓出系統資源。