什麼是多線程: 進程:正在運行的程式,QQ 360 ...... 線程:就是進程中一條執行程式的執行路徑,一個程式至少有一條執行路徑。(360中的殺毒 電腦體檢 電腦清理 同時運行的話就需要開啟多條路徑) 每個線程都有自己需要運行的內容,而這些內容可以稱為線程要執行的任務。 開啟多線程是為了同時運行 ...
什麼是多線程:
進程:正在運行的程式,QQ 360 ......
線程:就是進程中一條執行程式的執行路徑,一個程式至少有一條執行路徑。(360中的殺毒 電腦體檢 電腦清理 同時運行的話就需要開啟多條路徑)
每個線程都有自己需要運行的內容,而這些內容可以稱為線程要執行的任務。
開啟多線程是為了同時運行多部分代碼。
好處:解決了多部分需要同時運行的問題
弊端:如果線程過多,會導致效率很低(因為程式的執行都是CPU做著隨機 快速切換來完成的)
JVM在啟動時,就有著多個線程啟動
在多線程運行過程中,如有線程拋出了異常,那麼該線程會停止運行(出棧),但是並不影響其他線程的正常運行。
線程的四種狀態:
創建線程:
方法一:繼承Thread類
方法二:實現Runnable介面
分析:創建線程的目的是為了開闢一條執行路徑,讓線程去運行指定的代碼(執行路徑的任務),實現和其他線程同時執行任務。
JVM創建的主線程任務都定義在了mian方法中;自定義的線程任務將要封裝在Thread類的run方法中。
方法一的步驟:1:繼承Thread類 2:重寫run方法(封裝任務) 3:實例化Thread類的子類對象 4:調用start方法開啟線程(這個方法會調用run方法來執行任務)
1 class Text{ 2 public static void main(String[] args) { 3 aThread Dome=new aThread(); 4 Dome.start(); 5 System.out.println(Thread.currentThread().getName()); 6 } 7 } 8 class aThread extends Thread{ 9 public void run(){ 10 System.out.println(Thread.currentThread().getName()); 11 } 12 }
currentThread()是一個靜態方法,它返回的是當前正在運行線程的這個對象,然後調用getName方法返回這個線程的名字;
名字是Thread-編號(從0開始),主函數的名字就是main。
Thread類中有個接收String類型參數的構造方法,我們傳入的數據可以自定義線程的名字。如下代碼:
1 class aThread implements Runnable{ 2 aThread(String a){ 3 super(a); 4 } 5 public void run(){ 6 System.out.println(this.getName()); 7 } 8 }
如果一個子類已經有了父類,但是它需要開啟多線程來執行任務,那麼就要用到Runnable這個介面來擴展該子類的功能(避免了java的單繼承局限性)
Runnable這個介面只有一個run一個抽象方法,所以Runnable介面的出現僅僅是為了用run封裝任務而存在的;而且Thread類也實現了Runnable介面
方法二的步驟:1:實現Runnable介面 2:重寫run方法(封裝任務) 3:創建Thread線程a 創建Runnable子類對象t
4:將t作為參數傳遞給a(Thread有個構造方法是用來接收Runnable類型參數的;因為任務都封裝在了Runnbale子類的run方法中,
在開啟線程的時候就要明確線程的任務,否則Thread會調用自己的run方法,Runnbale子類中的任務將永遠不會被執行到)
5:開啟線程(start)
1 class Text{ 2 public static void main(String[] args) { 3 aThread t=new aThread(); 4 Thread a=new Thread(t); 5 a.start(); 6 System.out.println(Thread.currentThread().getName()); 7 } 8 } 9 class aThread implements Runnable{ 10 11 public void run(){ 12 System.out.println(Thread.currentThread().getName()); 13 } 14 }
實現Runnable介面的好處:1.將任務從Thread子類中分離出來,進行單獨的封裝;按照面向對象的思想將任務封裝成了對象
2.避免了JAVA單繼承的局限性
多線程安全問題:
原因: 1.多個線程操作共用數據
2.操作共用數據的代碼有多條
當一個線程在執行共用數據的多條代碼時,有其他的線程參與進來,就會導致線程安全問題的產生。
例:
1 class Text{ 2 public static void main(String[] args) { 3 aThread t=new aThread(); 4 Thread a=new Thread(t); 5 Thread a1=new Thread(t); 6 Thread a2=new Thread(t); 7 Thread a3=new Thread(t); 8 a.start(); 9 a1.start(); 10 a2.start(); 11 a3.start(); 12 } 13 } 14 class aThread implements Runnable{ 15 private int mun=100; 16 public void run(){ 17 while(mun>0){ 18 try{Thread.sleep(5);}catch(InterruptedException e){} 19 /*為了能夠看得清楚,讓線程凍結,sleep會拋出異常由於Runnable介面並沒有拋出異常 20 ,所以其子類也不能拋,只能try catch去捕捉異常。*/ 21 System.out.println(Thread.currentThread().getName()+"....."+mun--); 22 }/*運行的結果中會出項這樣的情況Thread-3.....42 Thread-0.....42 23 (如果是賣票的話我們可以理解為這兩個線程都賣了42號這個票,這是不允許的)*/ 24 } 25 }
解決問題:
思路:當有線程操作共用數據的代碼塊時,不允許其他線程參與進來,即同步(簡單說就是給共用數據的代碼塊安個鎖,線程進來的時候都要判斷一下是不是有別的線程正在操作共用數據代碼塊)
同步的好處:解決了安全問題
同步的弊端:相對而言效率降低了(因為每次都需要判斷鎖)
註意:這幾個線程一定要使用同一個鎖
方法一:同步代碼塊
方法二:同步函數
關鍵字:synchronized
實現方法一(同步代碼塊):
1 class Text{ 2 public static void main(String[] args) { 3 aThread t=new aThread(); 4 Thread a=new Thread(t); 5 Thread a1=new Thread(t); 6 Thread a2=new Thread(t); 7 Thread a3=new Thread(t); 8 a.start(); 9 a1.start(); 10 a2.start(); 11 a3.start(); 12 } 13 } 14 class aThread implements Runnable{ 15 private int mun=100; 16 private Object obj=new Object(); 17 public void run(){ 18 while(mun>0){ 19 synchronized(obj){ 20 if(mun<=0)return; 21 try{Thread.sleep(5);}catch(InterruptedException e){} 22 System.out.println(Thread.currentThread().getName()+"....."+mun--); 23 } 24 } 25 } 26 }
同步代碼塊的鎖是可以自定義的,這裡的鎖我自定義的是Obiect類的對象obj
實現方法二(同步函數):
1 class Text{ 2 public static void main(String[] args) { 3 aThread t=new aThread(); 4 Thread a=new Thread(t); 5 Thread a1=new Thread(t); 6 Thread a2=new Thread(t); 7 Thread a3=new Thread(t); 8 a.start(); 9 a1.start(); 10 a2.start(); 11 a3.start(); 12 } 13 } 14 class aThread implements Runnable{ 15 private int mun=100; 16 17 public void run(){ 18 while(mun>0) 19 show(); 20 } 21 public synchronized void show(){ 22 if(mun<=0)return; 23 try{Thread.sleep(5);}catch(InterruptedException e){} 24 System.out.println(Thread.currentThread().getName()+"....."+mun--); 25 } 26 }
同步函數的鎖是固定的this
靜態同步函數的鎖,是這個函數所屬的位元組碼對象(class文件)
建議使用同步代碼塊
死鎖:
同步的嵌套,有兩把鎖,都拿著對方的鎖,導致代碼無法繼續進行下去。
1 class Text{ 2 public static void main(String[] args) { 3 aThread a=new aThread(true); 4 aThread a1=new aThread(false); 5 Thread t=new Thread(a); 6 Thread t1=new Thread(a1); 7 t.start(); 8 t1.start(); 9 } 10 } 11 class aThread implements Runnable{ 12 private boolean flag; 13 aThread(boolean flag){ 14 this.flag=flag; 15 } 16 public void run(){ 17 if(flag){ 18 synchronized(suo.suo1){ 19 System.out.println("我是if鎖1"); 20 synchronized(suo.suo2){ 21 System.out.println("我是if鎖2"); 22 } 23 } 24 }else{ 25 synchronized(suo.suo2){ 26 System.out.println("我是elsef鎖2"); 27 synchronized(suo.suo1){ 28 System.out.println("我是else鎖1"); 29 } 30 } 31 } 32 } 33 } 34 class suo{ 35 public static final Object suo1=new Object(); 36 public static final Object suo2=new Object(); 37 }
懶漢式在多線程中的應用:
因為懶漢式並沒有直接實例化對象,線上程0判斷了if語句後進入臨時阻塞狀態,線程1也進入了進來,這就導致了實例化不唯一
實例化不唯一問題的示例代碼:
1 public class Text1 { 2 3 public static void main(String[] args) { 4 Ab a=new Ab(); 5 Ab b=new Ab(); 6 a.start(); 7 b.start(); 8 try{Thread.sleep(10);}catch(InterruptedException e){} 9 System.out.print(a.get()==b.get()); 10 } 11 } 12 class Single{ 13 private static Single a=null; 14 private Single(){ 15 } 16 static Single getSingle(){ 17 if(a==null){ 18 try{Thread.sleep(10);}catch(InterruptedException e){} 19 a=new Single(); 20 } 21 return a; 22 } 23 } 24 class Ab extends Thread{ 25 private Single a; 26 public void run(){ 27 a=Single.getSingle(); 28 } 29 Single get(){ 30 return a; 31 } 32 }
多試幾次輸出的結果就會出現false
解決問題(同步)(餓漢式代碼應該如下修改):
1 class Single{ 2 private static Single a=null; 3 private Single(){ 4 } 5 static Single getSingle(){ 6 if(a==null){ 7 synchronized (Single.class){ 8 if(a==null){ 9 a=new Single(); 10 } 11 } 12 } 13 return a; 14 } 15 }
註意:在原有的餓漢式代碼中多加了一個if判斷,是為了提高效率,不然的還線程總是會去判斷鎖,效率下降,這也是不時用同步函數的原因。
加了同步,是為瞭解決安全問題。
所以在開發的時候還是使用餓漢式好