《Think In Java》讀書筆記(二)多線程__Java

來源:互聯網
上載者:User

一、多線程基本概念
 獨佔:通過阻止多個並發行為間的有害幹擾來維護狀態的一致性。通常使用非同步方法呼叫sychronized
 狀態依賴:觸發,阻止,延遲,恢複,某些行為是由一些對象是否處在這些行為可能成功或者已經
 成功的狀態決定的。主要通過監視器(monitor)實現 object.wait object.notify, object.notifyAll

 客戶Client: Client Object 期待某個動作的執行
 服務Serverce :包括執行這個動作的代碼
 主體:客戶和服務都可以是一個對象,這個對象就是一個主體。而又他調用的對象通常就是輔助對象(help)

 Synchronized: 防止並法導致資料不一致,可以修飾一個方法或者代碼塊。
 1.Object 或子類的每一個執行個體都會在進入這個synchronized方法前加鎖,並在離開這個方法時自動釋放鎖,
 使用synchronized關鍵字來定義方法就會鎖定類中所有使用synchronzied關鍵字定義的靜態方法或非靜態方法
 不同的對象執行個體的synchronized方法是不相干擾的。也就是說,其它線程照樣可以同時訪問相同類的另一個對象執行個體中的synchronized方法;
 對於修飾靜態方法它可以對類的所有對象執行個體起作用。
 2.對代碼塊的加鎖也是,只是需要指明對那個對象加鎖。在靜態代碼塊中使用類.class來指明,
 在非靜態代碼塊中多數是用this
 3.synchronized關鍵字是不能繼承的,也就是說,基類的方法synchronized f(){} 在繼承類中並不自動是synchronized f(){},而是變成了f(){}。繼承類需要你顯式的指定它的某個方法為synchronized方法
 規則:
 1)永遠只是在更新對象的成員變數是加鎖
 2)永遠只是在訪問有可能被更新對象的成員變數時加鎖
 3)永遠不要在調用其它對象的方法時加鎖


二、 java.util.concurent 包介紹
 1、執行器(Executor) 用來管理Thread 對象,下面介紹三種Executor
  (1) newCachedThreadPool  
  ExecutorService exec = Executors.newCachedThreadPool();//建立一個可根據需要建立新線程的線程池
  for(int  = 0 ; i<5; i++){
   exec.execute(new LiftOff());//LiftOff implements Runnable介面的類
   exec.shutdown()
  }
  (2) newFixedThreadPool
  ExecutorService exec = Executors.newFixedThreadPool(5);//建立一個可重用固定線程數的線程池,以共用的無界隊列方式來運行這些線程。
  for(int  = 0 ; i<5; i++){
   exec.execute(new LiftOff());//LiftOff implements Runnable介面的類
   exec.shutdown()
  }
  (3) newSingleThreadPool
  SingleThreadPool 就好象是線程數量為1的FixedThreadPool,如果向SingleThreadPool提交多個任務,那麼這些任務都將排列,每個任務都會在下一個任務開始之前運行結束。
  ExecutorService exec = Executors.newSingleThreadPool();//建立一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。
  for(int  = 0 ; i<5; i++){
   exec.execute(new LiftOff());//LiftOff implements Runnable介面的類
   exec.shutdown()
  }

 2、從任務中產生傳回值
  Runnable是執行工作的獨立任務,但是不返回任何值,如果實現Callable 介面,則Callable介面方法call()可以返回一個值。
  package cn.com.cdl.chen;
  /**
   * @ Class CallableDemo.java
   * @ Description 
   * @ Company OpenData
   * @ Author Chenlly E-mail: Chenlly99@Gmail.com
   * @ Version 1.0
   * @ Date Mar 13, 2010
   */
  import java.util.ArrayList;
  import java.util.concurrent.Callable;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.Future;
  public class CallableDemo{
   public static void main(String[] args){
   
    ExecutorService exec = Executors.newCachedThreadPool();
    ArrayList<Future<String>> results = new ArrayList<Future<String>>();
    for(int i = 0; i < 10; i++){
     results.add(exec.submit(new TaskWithResult(i)));//這裡使用submit 方法會產生Future 對象
    }
   
    for(Future<String> fs:results){
     try{
      if(fs.isDone()){//檢查Future是否已經完成,或者不用isDone()來檢查,那麼get()將會阻塞,直到結果準備就緒。
       System.out.println(fs.get());//通過get()得到call()方法返回的值,
      }
     } catch(Exception ex){
      ex.printStackTrace();
     } finally{
      exec.shutdown();
     }
    }
   }
  }
  class TaskWithResult implements Callable<String>{
   private int id;
  
   public TaskWithResult(int id){
    this.id = id;
   }
  
   public String call(){
    return "result of TaskWithResult" + id;
   }
  
  
  }
  //結果:
  result of TaskWithResult0
  result of TaskWithResult1
  result of TaskWithResult2
  result of TaskWithResult3
  result of TaskWithResult4
  result of TaskWithResult5
  result of TaskWithResult6
  result of TaskWithResult7
  result of TaskWithResult8
  result of TaskWithResult9
  sleep() 和wait()方法的區別
  sleep是Thread類的靜態方法。sleep的作用是讓線程休眠制定的時間,在時間到達時恢複,也就是說sleep將在接到時間到達事件事恢複線程執行,其控制範圍是由當前線程塊
  wait是Object的方法,也就是說可以對任意一個對象調用wait方法,調用wait方法將會將調用者的線程掛起,直到其他線程調用同一個對象的notify方法才會重新啟用調用者
  sleep()是讓某個線程暫停運行一段時間,其控制範圍是由當前線程決定,也就是說,線上程裡面決定.好比如說,我要做的事情是 "點火->燒水->煮麵",而當我點完火之後我不立即燒水,我要休息一段時間再燒.對於啟動並執行主動權是由我的流程來控制.
  而wait(),首先,這是由某個確定的對象來調用的,將這個對象理解成一個傳話的人,當這個人在某個線程裡面說"暫停!",也是 thisOBJ.wait(),這裡的暫停是阻塞,還是"點火->燒水->煮飯",thisOBJ就好比一個監督我的人站在我旁邊,本來該線 程應該執行1後執行2,再執行3,
  而在2處被那個對象喊暫停,那麼我就會一直等在這裡而不執行3,但正個流程並沒有結束,我一直想去煮飯,但還沒被允許, 直到那個對象在某個地方說"通知暫停線程啟動!",也就是thisOBJ.notify()的時候,那麼我就可以煮飯了,這個被暫停線程就會從暫停處 繼續執行.
  本質的區別是一個線程的運行狀態控制,一個是線程之間的通訊的問題.
 
 3、t.join();方法
  package cn.com.cdl.chen;
  /**
   * @ Class Joining.java
   * @ Description 
   * @ Company OpenData
   * @ Author Chenlly E-mail: Chenlly99@Gmail.com
   * @ Version 1.0
   * @ Date Mar 14, 2010
   */
  class Sleeper extends Thread{
  
   private int duration;
  
   public Sleeper(String name, int sleepTime){
    super(name);
    duration = sleepTime;
    start();
   }
  
   public void run(){
    try{
     sleep(duration);
    }catch(InterruptedException ex){
     System.out.println(this.getName()+" was interrupted."+"isInterrupted():"+this.isInterrupted());
    }
   
    System.out.println(this.getName()+" has awakened");
   }
  }
  class Joiner extends Thread{
   private Sleeper sleeper;
  
   public Joiner(String name, Sleeper sleeper){
    super(name);
    this.sleeper = sleeper;
    start();
   }
  
   public void run(){
   
    try{
     sleeper.join();//線程Sleeper線上程Joiner上執行join(),則Joiner 線程將被掛起,直到sleeper 線程結束(isAlive()==false)。才恢複
    } catch(InterruptedException ex){
     System.out.print("Interrupted");
    }
   
    System.out.println(this.getName()+" join completed");
   }
  }

  public class Joining {
   public static void main(String []args){
    Sleeper sleepy = new Sleeper("Sleepy",1500);
    Sleeper grumpy = new Sleeper("Grumpy",1500);
   
    Joiner dopey = new Joiner("Dopey",sleepy);
    Joiner doc = new Joiner("Doc",grumpy);
   
    grumpy.interrupt();//grumpy 中斷,doc 將繼續
   }
  }

  out put:
  Grumpy was interrupted.isInterrupted():false//當線程在該線程上調用interrupt()時,將給該線程設定一個標誌,然而,異常在捕獲時會清理這個標誌,所以在catch 代碼塊裡調用interrupt()總是false
  Grumpy has awakened
  Doc join completed
  Sleepy has awakened
  Dopey join completed

 4、顯示加鎖,java.util.concurrent.locks
  public synchronized int next(){
   ...
   try{
    return
   }catch(){
  
   }
  }
  private Lock lock = new ReentrantLock();
  public  int next(){
   try{
    lock.lock();
    ...
    return
   } catch(){
    lock.unlock();
   }
  
  使用synchronized關鍵字和顯示Lock都可以解決資源同步的問題,並且synchronized 關鍵字比顯示加鎖代碼量少更少,但是當synchronized關鍵字不
  能嘗試擷取鎖一段時間,然後放棄它。

 5、執行緒區域化:java.lang.ThreadLocal
  它的功用非常簡單,就是為每一個使用該變數的線程都提供一個變數值的副本,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本衝突。從線程的角度看,就好像每一個線程都完全擁有該變數
  通常通過get(),set()方法來訪問該對象的內容,get()方法返回與其線程相關聯的對象副本,而set()方法將參數值插入到為其線程儲存的對象裡。
  package cn.com.cdl.chen;
  import java.util.Random;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.TimeUnit;
  class Accessor implements Runnable{
   private final int id;
   public Accessor(int idn){id = idn;}
   public void run(){
    while(!Thread.currentThread().isInterrupted()){
     ThreadLocalVariableHolder.increment();
     System.out.println(this);
     Thread.yield();
    }
   }
   public String toString(){
    return "#"+id+":"+ThreadLocalVariableHolder.get();
   }
  
  }
  public class ThreadLocalVariableHolder{
   private static ThreadLocal<Integer> value=
    new ThreadLocal<Integer>(){
    private Random rand = new Random(47);
    protected synchronized Integer initialValue(){
     return rand.nextInt(10000);
    }
   };
  
   public static void increment(){
    value.set(value.get() + 1);
   }
  
   public static int get(){return value.get();}
  
   public static void main(String []args) throws Exception{
    ExecutorService exec = Executors.newCachedThreadPool();
    for(int i = 0; i < 5; i++){
     exec.execute(new Accessor(i));
    }
    TimeUnit.SECONDS.sleep(3);
    exec.shutdownNow();
   }
  }
  //output:
  #2:35601
  #4:30424
  #0:37948
  #2:35602
  #4:30425
  #0:37949
  #2:35603
  #4:30426
  #0:37950
 
 6、線程中捕獲異常
  任務的run()方法通常總會有某種形式的迴圈,使得任務一直運行下去直到不再需要,所以要設定跳出迴圈的條件。
  通常,它被寫成無限迴圈的形式這就意味著,除非有個條件使用它終止,否則它將永遠運行下去。
  由於線程的本質特性,所以我們不能捕獲到從線程中逃逸的異常,比如:

  package cn.com.chenlly;

  public class ThreadException implements Runnable {

   public void run() {
    throw new RuntimeException("I'm throw Exception");
   }

   public static void main(String []args){
    try{
     Thread t = new Thread(new ThreadException());
     t.start();
     
    } catch(Exception ex){
     System.out.println("Exception has been handled!");
     ex.printStackTrace();
    }
   }
   
  }

  /* output
  Exception in thread "Thread-0" java.lang.RuntimeException: I'm throw Exception
   at cn.com.chenlly.ThreadException.run(ThreadException.java:6)
   at java.lang.Thread.run(Unknown Source)
  也就是說tyr catch 無法捕獲到從run 方法裡逃逸的異常。
  Thread.UncaughtExceptionHandler這個介面就是當 Thread 因未捕獲的異常而突然終止時,調用處理常式的介面。
  它允許你在每個Thread對象上都附著一個異常處理器。
  package cn.com.chenlly;

  public class ThreadException implements Runnable {

   public void run() {
    throw new RuntimeException("I'm throw Exception ");
   }

   public static void main(String[] args) {
    Thread t = new Thread(new ThreadException());
    t.setUncaughtExceptionHandler(new MyUncaughtException());//添加一個異常處理器
    t.start();
   }

  }

  //異常處理器
  class MyUncaughtException implements Thread.UncaughtExceptionHandler {
   public void uncaughtException(Thread t, Throwable e) {
    System.out.println(t + " : " + e.getMessage());
   }
  }


  /* output
  Thread[Thread-0,5,main] : I'm throw Exception


 7、線程之間的協助:
  多個任務,可以通過使用鎖或者synchronized(互斥)的方法來同步兩個線程的行為,從而使的一個任務不會幹涉另外一個任務的資源。
  那麼如何使得多個任務可以在一起彼此協作,去解決某個問題。使用相同的互斥基礎:互斥。互斥能夠確保只有一個任務可以響應某個訊號。

  eg:一個是將蠟塗到Car上,一個是拋光它。拋光任務在塗蠟完成之前是不能操作的,而塗蠟任務在塗另外一層蠟之前,必須等待拋光結束。
  car 對象通過wait,和notifyAll來掛起和重新啟動這些任務。
  package cn.com.chenlly;

  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.TimeUnit;

  class Car{
   private boolean waxOn = true;//預設為拋光狀態,同步訊號
   
   //打蠟完成,設定狀態,並且喚醒打蠟
   public synchronized void waxed(){
    waxOn = false;
    notifyAll();
   }
   
   //拋光完成,設定狀態,並且喚醒打蠟
   public synchronized void buffing(){
    waxOn = true;
    notifyAll();
   }
   
   //是否拋光結束
   public synchronized void waitForWaxed() throws InterruptedException{
    while(!waxOn){
     wait();
    }
   }
   
   //是否打蠟結束
   public synchronized void waitForBuffing() throws InterruptedException{
    while(waxOn){
     wait();
    }
   }
   
  }

  //打蠟
  class WaxOn  implements Runnable{
   private Car car;
   public WaxOn(Car car){
    this.car = car;
   }
   public void run() {
    try{
     while(!Thread.interrupted()){
      car.waitForWaxed();
      System.out.println("Wax On!");
      TimeUnit.SECONDS.sleep(2);
      car.waxed();
     }
    } catch (Exception ex){
     ex.printStackTrace();
    }
   }
  }
  //拋光
  class Buffing  implements Runnable{
   private Car car;
   public Buffing(Car car){
    this.car = car;
   }
   public void run() {
    try{
     while(!Thread.interrupted()){
      car.waitForBuffing();
      System.out.println("Buffing On!");
      TimeUnit.SECONDS.sleep(2);
      car.buffing();
     }
    } catch (Exception ex){
     ex.printStackTrace();
    }
   }
  }
  public class WaxOMatic {
   public static void main(String []args) {
    try{
    Car car = new Car();
    ExecutorService exec = Executors.newCachedThreadPool();
    exec.execute(new WaxOn(car));
    exec.execute(new Buffing(car));
    TimeUnit.SECONDS.sleep(10);
    exec.shutdownNow();
    } catch (InterruptedException ex){
     ex.printStackTrace();
    }
   }
  }

  /* output
  Wax On!
  Buffing On!
  Wax On!
  Buffing On!
  Wax On!
  Buffing On!

  打蠟和拋光相繼進行,控制權在這兩者之間相互傳遞時,這兩個步驟過程在不斷地重複。5秒後中斷這兩個任務。


 8、線程死結問題

  1.死結:某個任務在等待另外一個任務,而後者又在等待別的任務,這樣一直下去,任務之間連續的迴圈就是死結問題
  典型的哲學家就餐就是死結問題
  
  2.死結的四個必要條件:
   (1) 互斥條件:資源不能被共用,只能由一個進程使用。
   (2) 請求與保持條件(Hold and wait):已經得到資源的進程可以再次申請新的資源。
   (3) 非剝奪條件:經分配的資源不能從相應的進程中被強制地剝奪
   (4) 迴圈等待條件(Circular wait):系統中若干進程組成環路,該環路中每個進程都在等待相鄰進程正佔用的資源。
  
  3.處理死結的策略
  通過破除死結四個必要條件之一,來防止死結產生。

  哲學家就餐問題:該問題涉及到五個哲學家,他們交替地進行思考和進餐。
  他們分別坐在位於一個圓形餐桌周圍的五把椅子上,圓桌中央是一碗米飯,餐桌上共有五根筷子,
  分別擺放在每兩個相鄰座位的中間。
  當哲學家思考時,他不與其他人交談。當哲學家饑餓時,他將拿起和他相鄰的兩根筷子進行進餐,
  但他很可能僅拿到一根,此時旁邊的另一根正在他鄰居的手中。
  只有他同時拿到兩根筷子時他才能開始進餐。完成進餐後,
  他將兩根筷子分別放回原位,然後再次開始思考。

 

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.