Java 多線程技術 線程中斷解析

線程中斷

測試 interrupt() 方法是否能中斷線程:

// 測試中斷線程
public class Test{
    public static void main(String[] args){
      TestRunnable runnable = new TestRunnable();
      Thread thread = new Thread(runnable);
      thread.start();
      // 調用 interrupt() 方法
      thread.interrupt();
    }
}

class TestRunnable implements Runnable{

  @Override
  public void run(){
    int num = 0;
    // 測試線程是否中斷 初始線程啓動 爲false 調用 interrupt方法 變成爲true
    while(!Thread.currentThread().isInterrupted()){
      try{
      // 讓線程休眠
        Thread.sleep(1000);
      }catch(InterruptedException e){
        e.printStackTrace();
      }
      System.out.println("線程運行" + num++);
    }
  }
}

/*
* 從輸出情況來看 我們發現 線程循環並沒有出現中斷 
* 而且還拋出了 InterruptedException 異常
* 通過查詢相關文檔我們發現 interrupt 方法並非讓線程中斷
* 而是設置線程 isInterrupt() 布爾值 如果線程中斷 返回true 反之爲false
* 那麼 拋出異常是什麼回事呢?
* 我們發現當線程中有 wait()和 sleep()方法 在調用interrupt方法就會拋出這個異常
* 那麼我們應該如何才能讓線程停止呢?
*/

修改版本(僞代碼):

// 這裏我們不再使用 interrupt 方法 改用標記法
// 1. 首先初始化一個布爾變量 賦值爲false
// 2. 在將它放入run() 中的循環條件上
// 3. 在主函數中設置停止線程

public class Test{
    public static void main(String[] args){
      TestRunnable runnable = new TestRunnable();
      Thread thread = new Thread(runnable);
      thread.start();
      runnable.flag = true;
    }
}

class TestRunnable implements Runnable{
  boolean flag = false;
  @Override
  public void run(){
    int num = 0;
    while(!flag){
         // do something
    }
  }
}

上面代碼中我們遇到了 線程中有sleep()方法 在調用 Interrupt() 會拋出異常
現在我們研究一下這個異常到底是什麼

public class Demo03 {
    public static void main(String[] args) {
        InterruptRunnable t1 = new InterruptRunnable();
        InterruptRunnable t2 = new InterruptRunnable();
        t1.start();
        t2.start();

        for (int i = 0; i < 50; i++) {
            if (i == 25) {
                // 調用中斷方法 來清除狀態
                t1.interrupt();
                t2.interrupt();
                break;
            }
            System.out.println(i + "----");
        }
        System.out.println("主線程結束");
    }
}

class InterruptRunnable extends Thread{

    @Override
    public synchronized void run() {
        while (true) {
            try {
                /*
                 * 線程一 進入 遇到 wait()方法
                 * 放棄 CPU 的執行權 但是鎖會還回去
                 * 線程二 進入 同理 
                 * 相當於兩個線程都進入了冷凍(中斷)狀態
                 * 解決冷凍狀態 
                 * 調用 interrupt() 清除該狀態
                 */
                wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }   
            System.out.println(Thread.currentThread().getName() + "---" + "run..");
        }
    }
}

// 之前已經測試出 有sleep()方法 在調用 Interrupt() 會拋出異常  
// InterruptedException
// 這個是當線程在活動之前或活動期間處於正在等待、休眠或佔用狀態且該線程被中斷時,拋出該異常
// 因此我們不推薦使用 interruot() 方法來結束線程 推薦使用標記法
// 當我們開始測試 wait() 時 發現拋出 IllegalMonitorStateException 異常
// 這是個對象監視器 在 沒有同步鎖的時候會拋出 因爲 wait() 是讓調用它的線程進行等待 
// 必須使用對象鎖 

線程等待與喚醒

代碼示例:

/*
 * Person類 姓名 性別
 * 開啓兩個線程
 * 一個對Person對象進行賦值
 * 一個對Person對象進行打印
 * 要求
 * 一次打印 james man
 * 一次打印 JAMES MAN
 * 間隔輸出
*/

public class Test{
      public static void main(String[] args){
      // 線程的創建
            Persons person = new Persons();
      //   這裏要保證操作的數據是同一個 使用構造方法即可!
            SetRunnerable set = new SetRunnerable(person);
            PrintRunnable print = new PrintRunnable(person);

            Thread setPerson = new Thread(set);
            Thread printPerson = new Thread(print);

            setPerson.start();
            printPerson.start();
      }
    }

class SetRunnerable implements Runnable{
        private boolean sigin = true;
      // 定義一個成語變量在構造方法中接收
        private Persons p ;
        public SetRunnerable(){}

        public SetRunnerable(Persons person){
          this.p = person;
        }

        @Override
        public void run(){
          while(true){
          // 同步鎖要求鎖的唯一性 因此使用兩個線程共同使用的對象鎖
            synchronized(p){
            // 爲滿足間隔輸出
            // 在person類中定義一個變量 在這裏進行判斷
            // 先不進行線程等待 直接賦值
              if (p.flag == true) {
                try{
                  p.wait();
                }catch(InterruptedException e){
                  e.printStackTrace();
                }}
              // 控制 sigin 賦值兩個變量
                  if (sigin) {
                    p.name = "james";
                    p.sex = "man";
                  }else{
                    p.name = "JAMES";
                    p.sex = "MAN";
                  }
                // 賦值完成後 進行反轉 進行下一輪賦值
                  sigin = !sigin;
                // 在進行賦值結束後 應該進行打印 因此讓本線程進行等待
                // 當另一個線程打印結束返回 false 纔會進行下次賦值
                  p.flag = true;
                // 喚醒等待中的線程
                  p.notify();


            }
          }
        }
    }

    class PrintRunnable implements Runnable{
        private Persons p ;
         public PrintRunnable(){}

         public PrintRunnable(Persons person){
           this.p = person;
         }
         @Override
         public void run(){
           while(true){
             synchronized(p){
               if (p.flag == false) {
                   try{
                     p.wait();
                   }catch(InterruptedException e){
                     e.printStackTrace();
                   }
               }
               System.out.println(p.name + "---" + p.sex);
               p.flag = false;
               p.notify();
             }
           }
         }
    }

class Persons{
      public String name;
      public String sex;
      boolean flag = false;
    }

單例

瞭解線程鎖後, 我們重新審視了一下單例的懶漢式 假如 有兩個線程同時訪問我們都的對象會不會出現創建兩個對象呢?
答案顯然是會的 這樣很明顯就與我們創建懶漢式的初衷相悖了 在引入對象鎖後 單例 懶漢式就比較安全了

// 單例 懶漢式
public class Test{
    // do somthing
}

class Sigin {
  Sigin sigin = null;
  public static synchronized Sigin getInstance(){
    if (sigin == null) {
      sigin = new Sigin();
    }
    return sigin;
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章