Java基礎:多線程

     進程:正在進行中的程序。其實進程就是一個應用程序運行時的內存分配空間。 
     線程:其實就是進程中一個程序執行控制單元,一條執行路徑。進程負責的是應用程序的空間的標示。線程負責的是應用程序的執行順序。  
      一個進程至少有一個線程在運行,當一個進程中出現多個線程時,就稱這個應用程序是多線程應用程序,每個線程在棧區中都有自己的執行空間,自己的方法區、自己的變量。 
      jvm在啓動的時,首先有一個主線程,負責程序的執行,調用的是main函數。主線程執行的代碼都在main方法中。 當產生垃圾時,收垃圾的動作,是不需要主線程來完成,因爲這樣,會出現主線程中的代碼執行會停止,會去運行垃圾回收器代碼,效率較低,所以由單獨一個線程來負責垃圾回收。   
       隨機性的原理:因爲cpu的快速切換造成,哪個線程獲取到了cpu的執行權,哪個線程就執行。  
       返回當前線程的名稱:Thread.currentThread().getName() 

       線程的名稱是由:Thread-編號定義的。編號從0開始。 線程要運行的代碼都統一存放在了run方法中。  線程要運行必須要通過類中指定的方法開啓。start方法。(啓動後,就多了一條執行路徑) 
       start方法:1)、啓動了線程;2)、讓jvm調用了run方法。  
       創建線程的第一種方式:繼承Thread ,由子類複寫run方法。 

      步驟: 
     1,定義類繼承Thread類; 
     2,目的是複寫run方法,將要讓線程運行的代碼都存儲到run方法中;
     3,通過創建Thread類的子類對象,創建線程對象; 
     4,調用線程的start方法,開啓線程,並執行run方法。  
     線程狀態: 
      被創建:start() 
      運行:   具備執行資格,同時具備執行權; 
      凍結:   sleep(time),wait()—notify()喚醒;線程釋放了執行權,同時釋放執行資格;
       臨時阻塞狀態:線程具備cpu的執行資格,沒有cpu的執行權;
      消亡:stop() 
 
       創建線程的第二種方式:實現一個接口Runnable。

       步驟: 
      1,定義類實現Runnable接口。 
      2,覆蓋接口中的run方法(用於封裝線程要運行的代碼)。
      3,通過Thread類創建線程對象; 
      4,將實現了Runnable接口的子類對象作爲實際參數傳遞給Thread類中的構造函數。 
      爲什麼要傳遞呢?因爲要讓線程對象明確要運行的run方法所屬的對象。 
      5,調用Thread對象的start方法。開啓線程,並運行Runnable接口子類中的run方法。 
     

     Ticket t = new Ticket(); 
     /* 
      直接創建Ticket對象,並不是創建線程對象。 
      因爲創建對象只能通過new Thread類,或者new Thread類的子類纔可以。   
      所以最終想要創建線程。既然沒有了Thread類的子類,就只能用         
      Thread類。   
      */ 
     Thread t1 = new Thread(t); //創建線程。   
     /* 
      只要將t作爲Thread類的構造函數的實際參數傳入即可完成線程對象和t之間的關聯   
      爲什麼要將t傳給Thread類的構造函數呢?其實就是爲了明確線程要運行的代碼run方法。 
      */ 
     t1.start(); 

         爲什麼要有Runnable接口的出現? 
         1:通過繼承Thread類的方式,可以完成多線程的建立。但是這種方式有一個侷限性,如果一個類已經有了自己的父類,就不可以繼承Thread類,因爲java單繼承的侷限性。 
        可是該類中的還有部分代碼需要被多個線程同時執行。這時怎麼辦呢? 
        只有對該類進行額外的功能擴展,java就提供了一個接口Runnable。這個接口中定義了run方法,其實run方法的定義就是爲了存儲多線程要運行的代碼。 
        所以,通常創建線程都用第二種方式。 因爲實現Runnable接口可以避免單繼承的侷限性。  
        2:其實是將不同類中需要被多線程執行的代碼進行抽取。將多線程要運行的代碼的位置單獨定義到接口中。爲其他類進行功能擴展提供了前提。 所以Thread類在描述線程時,內部定義的run方法,也來自於Runnable接口。   
        實現Runnable接口可以避免單繼承的侷限性。而且,繼承Thread,是可以對Thread類中的方法,進行子類複寫的。但是不需要做這個複寫動作的話,只爲定義線程代碼存放位置,實現Runnable接口更方便一些。所以Runnable接口將線程要執行的任務封裝成了對象。 

          

------------------------------------------------------- 
//面試 
  new Thread(new Runnable(){  //匿名   
 public void run(){ 
    System.out.println("runnable run");     
  }   
 })   
 { 
   public void run(){ 
    System.out.println("subthread run");    
   } 
  }.start();  //結果:subthread run 
------------------------------------------------------------

  1. 同步代碼塊使用的鎖可以是任意對象。 
  2. 同步函數使用的鎖是this,靜態同步函數的鎖是該類的字節碼文件對象。  
  3.  在一個類中只有一個同步的話,可以使用同步函數。如果有多同步,必須使用同步代碼塊,來確定不同的鎖。所以同步代碼塊相對靈活一些。 
------------------------------------------------------- 
       ★考點問題:請寫一個延遲加載的單例模式?寫懶漢式;當出現多線程訪問時怎麼解決?加同步,解決安全問題;效率高嗎?不高;怎樣解決?通過雙重判斷的形式解決。 
       //懶漢式:延遲加載方式。 

       當多線程訪問懶漢式時,因爲懶漢式的方法內對共性數據進行多條語句的操作。所以容易出現線程安全問題。爲了解決,加入同步機制,解決安全問題。但是卻帶來了效率降低。 
      爲了效率問題,通過雙重判斷的形式解決。    

    

class Single{ 
 private static Single s = null;  private Single(){} 
 public static Single getInstance(){ //鎖是誰?字節碼文件對象;  
 if(s == null){ 
   synchronized(Single.class){     
     if(s == null) 
     s = new Single();   
   }  
   }  
  return s;  
} 
} 

--------------------------------------------------------- 
         同步死鎖:通常只要將同步進行嵌套,就可以看到現象。同步函數中有同步代碼塊,同步代碼塊中還有同步函數。  
         線程間通信:

         思路:多個線程在操作同一個資源,但是操作的動作卻不一樣。
         1:將資源封裝成對象。 
         2:將線程執行的任務(任務其實就是run方法。)也封裝成對象。  
         等待喚醒機制:涉及的方法: 
         wait:將同步中的線程處於凍結狀態。釋放了執行權,釋放了資格。同時將線程對象存儲到線程池中。 
         notify:喚醒線程池中某一個等待線程。 notifyAll:喚醒的是線程池中的所有線程。  
         注意: 
        1:這些方法都需要定義在同步中。 
        2:因爲這些方法必須要標示所屬的鎖。 
        你要知道 A鎖上的線程被wait了,那這個線程就相當於處於A鎖的線程池中,只能A鎖的notify喚醒。 
        3:這三個方法都定義在Object類中。爲什麼操作線程的方法定義在Object類中? 
         因爲這三個方法都需要定義同步內,並標示所屬的同步鎖,既然被鎖調用,而鎖又可以是任意對象,那麼能被任意對象調用的方法一定定義在Object類中。 
         wait和sleep區別: 分析這兩個方法:從執行權和鎖上來分析: 
         wait:可以指定時間也可以不指定時間。不指定時間,只能由對應的notify或者notifyAll來喚醒。 
         sleep:必須指定時間,時間到自動從凍結狀態轉成運行狀態(臨時阻塞狀態)。 

         wait:線程會釋放執行權,而且線程會釋放鎖。            

         Sleep:線程會釋放執行權,但不是不釋放鎖。  
         線程的停止:通過stop方法就可以停止線程。但是這個方式過時了。 
          停止線程:原理就是:讓線程運行的代碼結束,也就是結束run方法。 
         怎結束麼run方法?一般run方法裏肯定定義循環。所以只要結束循環即可。

          第一種方式:定義循環的結束標記。 
          第二種方式:如果線程處於了凍結狀態,是不可能讀到標記的,這時就需要通過Thread類中的interrupt方法,將其凍結狀態強制清除。讓線程恢復具備執行資格的狀態,讓線程可以讀到標記,並結束。  
---------< java.lang.Thread >---------- 
interrupt():中斷線程。 
setPriority(int newPriority):更改線程的優先級。
 getPriority():返回線程的優先級。 
toString():返回該線程的字符串表示形式,包括線程名稱、優先級和線程組。 
Thread.yield():暫停當前正在執行的線程對象,並執行其他線程。 
setDaemon(true):將該線程標記爲守護線程或用戶線程。將該線程標記爲守護線程或用戶線程。當正在運行的線程都是守護線程時,Java 虛擬機退出。該方法必須在啓動線程前調用。
 join:臨時加入一個線程的時候可以使用join方法。 
當A線程執行到了B線程的join方式。A線程處於凍結狀態,釋放了執行權,B開始執行。A什麼時候執行呢?只有當B線程運行結束後,A才從凍結狀態恢復運行狀態執行。   
LOCK的出現替代了同步:lock.lock();„„„lock.unlock(); 
Lock接口:多線程在JDK1.5版本升級時,推出一個接口Lock接口。 
解決線程安全問題使用同步的形式,(同步代碼塊,要麼同步函數)其實最終使用的都是鎖機制。  
到了後期版本,直接將鎖封裝成了對象。線程進入同步就是具備了鎖,執行完,離開同步,就是釋放了鎖。 
在後期對鎖的分析過程中,發現,獲取鎖,或者釋放鎖的動作應該是鎖這個事物更清楚。所以將這些動作定義在了鎖當中,並把鎖定義成對象。  
所以同步是隱示的鎖操作,而Lock對象是顯示的鎖操作,它的出現就替代了同步。  
在之前的版本中使用Object類中wait、notify、notifyAll的方式來完成的。那是因爲同步中的鎖是任意對象,所以操作鎖的等待喚醒的方法都定義在Object類中。  
而現在鎖是指定對象Lock。所以查找等待喚醒機制方式需要通過Lock接口來完成。而Lock接口中並沒有直接操作等待喚醒的方法,而是將這些方式又單獨封裝到了一個對象中。這個對象就是Condition,將Object中的三個方法進行單獨的封裝。並提供了功能一致的方法 await()、signal()、signalAll()體現新版本對象的好處。 
< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();
 

class BoundedBuffer { 
   final Lock lock = new ReentrantLock(); 
   final Condition notFull  = lock.newCondition();     
  final Condition notEmpty = lock.newCondition();     
  final Object[] items = new Object[100];    
  int putptr, takeptr, count; 
   public void put(Object x) throws InterruptedException {      
   lock.lock();      
  try { while (count == items.length)           
      notFull.await();       
      items[putptr] = x;  
       if (++putptr == items.length) 
       putptr = 0;        
        ++count; 
       notEmpty.signal();     
 }   finally { 
       lock.unlock();      
 }   
 } 
   public Object take() throws InterruptedException {      
        lock.lock();     
     try { 
       while (count == 0)          
       notEmpty.await(); 
       Object x = items[takeptr];  
       if (++takeptr == items.length) 
       takeptr = 0;        --count; 
       notFull.signal();      
      return x;      
    }  
finally { 
       lock.unlock();     
          }    
   }   
} 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章