多線程_基本操作


什么是多線程:

  進程:正在運行的程序,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判斷,是為了提高效率,不然的還線程總是會去判斷鎖,效率下降,這也是不時用同步函數的原因。

   加了同步,是為了解決安全問題。

所以在開發的時候還是使用餓漢式好

 

 

 

 

 

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM