欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

面試題:Java中如何停止線程的方法

 更新時(shí)間:2019年09月11日 15:33:52   作者:Java Geek Tech  
這篇文章主要介紹了Java中如何停止線程的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

如何停止線程是Java并發(fā)面試中的常見問題,本篇文章將從答題思路到答題細(xì)節(jié)給出一些參考。

答題思路:

  • 停止線程的正確方式是使用中斷
  • 想停止線程需要停止方,被停止方,被停止方的子方法相互配合
  • 擴(kuò)展到常見的錯(cuò)誤停止線程方法:已被廢棄的stop/suspend,無法喚醒阻塞線程的volatile

1. 正確方式是中斷

其實(shí)從邏輯上也很好理解的,一個(gè)線程正在運(yùn)行,如何讓他停止?

A. 從外部直接調(diào)用該線程的stop方法,直接把線程停下來。

B. 從外部通過中斷通知線程停止,然后切換到被停止的線程,該線程執(zhí)行一系列邏輯后自己停止。

很明顯B方法要比A方法好很多,A方法太暴力了,你根本不知道被停止的線程在執(zhí)行什么任務(wù)就直接把他停止了,程序容易出問題;而B方法把線程停止交給線程本身去做,被停止的線程可以在自己的代碼中進(jìn)行一些現(xiàn)場保護(hù)或者打印錯(cuò)誤日志等方法再停止,更加合理,程序也更具健壯性。

下面要講的是線程如何能夠響應(yīng)中斷,第一個(gè)方法是通過循環(huán)不斷判斷自身是否產(chǎn)生了中斷:

public class Demo1 implements Runnable{

  @Override
  public void run() {
    int num = 0;
    while(num <= Integer.MAX_VALUE / 2 && !Thread.currentThread().isInterrupted()){
      if(num % 10000 == 0){
        System.out.println(num);
      }
      num++;
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Demo1());
    thread.start();
    thread.sleep(1000);
    thread.interrupt();
  }
}

在上面的代碼中,我們?cè)谘h(huán)條件中不斷判斷線程本身是否產(chǎn)生了中斷,如果產(chǎn)生了中斷就不再打印

還有一個(gè)方法是通過java內(nèi)定的機(jī)制響應(yīng)中斷:當(dāng)線程調(diào)用sleep(),wait()方法后進(jìn)入阻塞后,如果線程在阻塞的過程中被中斷了,那么線程會(huì)捕獲或拋出一個(gè)中斷異常,我們可以根據(jù)這個(gè)中斷異常去控制線程的停止。具體代碼如下:

public class Demo3 implements Runnable {
  @Override
  public void run() {
    int num = 0;
    try {
      while(num < Integer.MAX_VALUE / 2){
        if(num % 100 == 0){
          System.out.println(num);
        }
        num++;
        Thread.sleep(10);
      }
    } catch (InterruptedException e) {//捕獲中斷異常,在本代碼中,出現(xiàn)中斷異常后將退出循環(huán)
      e.printStackTrace();
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Demo3());
    thread.start();
    Thread.sleep(5000);
    thread.interrupt();
  }
}

2. 各方配合才能完美停止

在上面的兩段代碼中已經(jīng)可以看到,想通過中斷停止線程是個(gè)需要多方配合。上面已經(jīng)演示了中斷方和被中斷方的配合,下面考慮更多的情況:假如要被停止的線程正在執(zhí)行某個(gè)子方法,這個(gè)時(shí)候該如何處理中斷?

有兩個(gè)辦法:第一個(gè)是把中斷傳遞給父方法,第二個(gè)是重新設(shè)置當(dāng)前線程為中斷。

先說第一個(gè)例子:在子方法中把中斷異常上拋給父方法,然后在父方法中處理中斷:

public class Demo4 implements Runnable{

  @Override
  public void run() {
    try{//在父方法中捕獲中斷異常
      while(true){
        System.out.println("go");
        throwInterrupt();
      }
    }catch (InterruptedException e) {
      e.printStackTrace();
      System.out.println("檢測到中斷,保存錯(cuò)誤日志");
    }
  }

  private void throwInterrupt() throws InterruptedException {//把中斷上傳給父方法
    Thread.sleep(2000);
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Demo4());
    thread.start();
    Thread.sleep(1000);
    thread.interrupt();
  }
}

第二個(gè)例子:在子方法中捕獲中斷異常,但是捕獲以后當(dāng)前線程的中斷控制位將被清除,父方法執(zhí)行時(shí)將無法感知中斷。所以此時(shí)在子方法中重新設(shè)置中斷,這樣父方法就可以通過對(duì)中斷控制位的判斷來處理中斷:

public class Demo5 implements Runnable{

  @Override
  public void run() {
    while(true && !Thread.currentThread().isInterrupted()){//每次循環(huán)判斷中斷控制位
      System.out.println("go");
      throwInterrupt();
    }
    System.out.println("檢測到了中斷,循環(huán)打印退出");
  }

  private void throwInterrupt(){
    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();//重新設(shè)置中斷
      e.printStackTrace();
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Demo5());
    thread.start();
    Thread.sleep(1000);
    thread.interrupt();
  }
}

講到這里,正確的停止線程方法已經(jīng)講的差不多了,下面我們看一下常見的錯(cuò)誤停止線程的例子:

3. 常見錯(cuò)誤停止線程例子:

這里介紹兩種常見的錯(cuò)誤,先說比較好理解的一種,也就是開頭所說的,在外部直接把運(yùn)行中的線程停止掉。這種暴力的方法很有可能造成臟數(shù)據(jù)。

看下面的例子:

public class Demo6 implements Runnable{
  /**
   * 模擬指揮軍隊(duì),以一個(gè)連隊(duì)為單位領(lǐng)取武器,一共有5個(gè)連隊(duì),一個(gè)連隊(duì)10個(gè)人
   */
  @Override
  public void run() {
    for(int i = 0; i < 5; i++){
      System.out.println("第" + (i + 1) + "個(gè)連隊(duì)開始領(lǐng)取武器");
      for(int j = 0; j < 10; j++){
        System.out.println("第" + (j + 1) + "個(gè)士兵領(lǐng)取武器");
        try {
          Thread.sleep(100);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
      System.out.println("第" + (i + 1) + "個(gè)連隊(duì)領(lǐng)取武器完畢");
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Demo6());
    thread.start();
    Thread.sleep(2500);
    thread.stop();
  }
}

在上面的例子中,我們模擬軍隊(duì)發(fā)放武器,規(guī)定一個(gè)連為一個(gè)單位,每個(gè)連有10個(gè)人。當(dāng)我們直接從外部通過stop方法停止武器發(fā)放后。很有可能某個(gè)連隊(duì)正處于發(fā)放武器的過程中,導(dǎo)致部分士兵沒有領(lǐng)到武器。

這就好比在生產(chǎn)環(huán)境中,銀行以10筆轉(zhuǎn)賬為一個(gè)單位進(jìn)行轉(zhuǎn)賬,如果線程在轉(zhuǎn)賬的中途被突然停止,那么很可能會(huì)造成臟數(shù)據(jù)。

另外一個(gè)“常見”錯(cuò)誤可能知名度不是太高,就是:通過volatile關(guān)鍵字停止線程。具體來說就是通過volatile關(guān)鍵字定義一個(gè)變量,通過判斷變量來停止線程。這個(gè)方法表面上是沒問題的,我們先看這個(gè)表面的例子:

public class Demo7 implements Runnable {

  private static volatile boolean canceled = false;

  @Override
  public void run() {
    int num = 0;
    while(num <= Integer.MAX_VALUE / 2 && !canceled){
      if(num % 100 == 0){
        System.out.println(num + "是100的倍數(shù)");
      }
      num++;
    }
    System.out.println("退出");
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Demo7());
    thread.start();
    Thread.sleep(1000);
    canceled = true;
  }
}

但是這個(gè)方法有一個(gè)潛在的大漏洞,就是若線程進(jìn)入了阻塞狀態(tài),我們將不能通過修改volatile變量來停止線程,看下面的生產(chǎn)者消費(fèi)者例子:

/**
 * 通過生產(chǎn)者消費(fèi)者模式演示volatile的局限性,volatile不能喚醒已經(jīng)阻塞的線程
 * 生產(chǎn)者生產(chǎn)速度很快,消費(fèi)者消費(fèi)速度很慢,通過阻塞隊(duì)列存儲(chǔ)商品
 */
public class Demo8 {
  public static void main(String[] args) throws InterruptedException {
    ArrayBlockingQueue storage = new ArrayBlockingQueue(10);

    Producer producer = new Producer(storage);
    Thread producerThread = new Thread(producer);
    producerThread.start();
    Thread.sleep(1000);//1s足夠讓生產(chǎn)者把阻塞隊(duì)列塞滿

    Consumer consumer = new Consumer(storage);
    while(consumer.needMoreNums()){
      System.out.println(storage.take() + "被消費(fèi)");
      Thread.sleep(100);//讓消費(fèi)者消費(fèi)慢一點(diǎn),給生產(chǎn)者生產(chǎn)的時(shí)間
    }

    System.out.println("消費(fèi)者消費(fèi)完畢");
    producer.canceled = true;//讓生產(chǎn)者停止生產(chǎn)(實(shí)際情況是不行的,因?yàn)榇藭r(shí)生產(chǎn)者處于阻塞狀態(tài),volatile不能喚醒阻塞狀態(tài)的線程)

  }
}

class Producer implements Runnable{

  public volatile boolean canceled = false;

  private BlockingQueue storage;

  public Producer(BlockingQueue storage) {
    this.storage = storage;
  }

  @Override
  public void run() {
    int num = 0;
    try{
      while(num < Integer.MAX_VALUE / 2 && !canceled){
        if(num % 100 == 0){
          this.storage.put(num);
          System.out.println(num + "是100的倍數(shù),已經(jīng)被放入倉庫");
        }
        num++;
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }finally {
      System.out.println("生產(chǎn)者停止生產(chǎn)");
    }
  }
}

class Consumer{
  private BlockingQueue storage;

  public Consumer(BlockingQueue storage) {
    this.storage = storage;
  }

  public boolean needMoreNums(){
    return Math.random() < 0.95 ? true : false;
  }
}

上面的例子運(yùn)行后會(huì)發(fā)現(xiàn)生產(chǎn)線程一直不能停止,因?yàn)樗幱谧枞麪顟B(tài),當(dāng)消費(fèi)者線程退出后,沒有任何東西能喚醒生產(chǎn)者線程。

這種錯(cuò)誤用中斷就很好解決:

/**
 * 通過生產(chǎn)者消費(fèi)者模式演示volatile的局限性,volatile不能喚醒已經(jīng)阻塞的線程
 * 生產(chǎn)者生產(chǎn)速度很快,消費(fèi)者消費(fèi)速度很慢,通過阻塞隊(duì)列存儲(chǔ)商品
 */
public class Demo8 {
  public static void main(String[] args) throws InterruptedException {
    ArrayBlockingQueue storage = new ArrayBlockingQueue(10);

    Producer producer = new Producer(storage);
    Thread producerThread = new Thread(producer);
    producerThread.start();
    Thread.sleep(1000);//1s足夠讓生產(chǎn)者把阻塞隊(duì)列塞滿

    Consumer consumer = new Consumer(storage);
    while(consumer.needMoreNums()){
      System.out.println(storage.take() + "被消費(fèi)");
      Thread.sleep(100);//讓消費(fèi)者消費(fèi)慢一點(diǎn),給生產(chǎn)者生產(chǎn)的時(shí)間
    }

    System.out.println("消費(fèi)者消費(fèi)完畢");
    producerThread.interrupt();
  }
}

class Producer implements Runnable{

  private BlockingQueue storage;

  public Producer(BlockingQueue storage) {
    this.storage = storage;
  }

  @Override
  public void run() {
    int num = 0;
    try{
      while(num < Integer.MAX_VALUE / 2 && !Thread.currentThread().isInterrupted()){
        if(num % 100 == 0){
          this.storage.put(num);
          System.out.println(num + "是100的倍數(shù),已經(jīng)被放入倉庫");
        }
        num++;
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }finally {
      System.out.println("生產(chǎn)者停止生產(chǎn)");
    }
  }
}

class Consumer{
  private BlockingQueue storage;

  public Consumer(BlockingQueue storage) {
    this.storage = storage;
  }

  public boolean needMoreNums(){
    return Math.random() < 0.95 ? true : false;
  }
}

4. 擴(kuò)展

可能你還會(huì)問:如何處理不可中斷的阻塞?

只能說很遺憾沒有一個(gè)通用的解決辦法,我們需要針對(duì)特定的鎖或io給出特定的解決方案。對(duì)于這些特殊的例子,api一般會(huì)給出可以響應(yīng)中斷的操作方法,我們要選用那些特定的方法,沒有萬能藥。

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java基于JavaMail實(shí)現(xiàn)向QQ郵箱發(fā)送郵件

    Java基于JavaMail實(shí)現(xiàn)向QQ郵箱發(fā)送郵件

    這篇文章主要為大家詳細(xì)介紹了Java基于JavaMail實(shí)現(xiàn)向QQ郵箱發(fā)送郵件的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-01-01
  • 高價(jià)值Java多線程面試題分析

    高價(jià)值Java多線程面試題分析

    Java?給多線程編程提供了內(nèi)置的支持。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。多線程是多任務(wù)的一種特別的形式,但多線程使用了更小的資源開銷
    2022-03-03
  • 解析Nacos的API居然存在這么嚴(yán)重的漏洞

    解析Nacos的API居然存在這么嚴(yán)重的漏洞

    這篇文章主要介紹了Nacos的API居然存在這么嚴(yán)重的漏洞,Nacos為我們提供了大量API,但是這些API默認(rèn)是沒有開啟認(rèn)證的,直接可以訪問,針對(duì)于這一點(diǎn)我們也都可以去驗(yàn)證一下,本文給大家詳細(xì)講解,感興趣的朋友跟隨小編一起看看吧
    2022-09-09
  • 簡單了解Spring Framework5.0新特性

    簡單了解Spring Framework5.0新特性

    這篇文章主要介紹了簡單了解Spring Framework5.0新特性,涉及了核心框架修訂,核心容器更新,使用Kotlin進(jìn)行函數(shù)式編程等幾個(gè)方面的介紹,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-11-11
  • Java硬幣翻轉(zhuǎn)倍數(shù)遞增試算實(shí)例

    Java硬幣翻轉(zhuǎn)倍數(shù)遞增試算實(shí)例

    這篇文章主要介紹了Java硬幣翻轉(zhuǎn)倍數(shù)遞增試算實(shí)例,有需要的朋友可以參考一下
    2013-12-12
  • Java中IO流的BufferedOutputStream和FileOutputStream對(duì)比

    Java中IO流的BufferedOutputStream和FileOutputStream對(duì)比

    這篇文章主要介紹了Java中IO流的BufferedOutputStream和FileOutputStream對(duì)比,不帶緩沖的操作,每讀一個(gè)字節(jié)就要寫入一個(gè)字節(jié),由于涉及磁盤的IO操作相比內(nèi)存的操作要慢很多,所以在讀寫的字節(jié)比較少的情況下,效率比較低,需要的朋友可以參考下
    2023-07-07
  • SpringBoot中的Spring Cloud Hystrix原理和用法詳解

    SpringBoot中的Spring Cloud Hystrix原理和用法詳解

    在Spring Cloud中,Hystrix是一個(gè)非常重要的組件,Hystrix可以幫助我們構(gòu)建具有韌性的分布式系統(tǒng),保證系統(tǒng)的可用性和穩(wěn)定性,在本文中,我們將介紹SpringBoot中的Hystrix,包括其原理和如何使用,需要的朋友可以參考下
    2023-07-07
  • java處理csv文件上傳示例詳解

    java處理csv文件上傳示例詳解

    這篇文章主要為大家詳細(xì)介紹了java處理csv文件上傳示例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • Springmvc基于fastjson實(shí)現(xiàn)導(dǎo)包及配置文件

    Springmvc基于fastjson實(shí)現(xiàn)導(dǎo)包及配置文件

    這篇文章主要介紹了Springmvc基于fastjson實(shí)現(xiàn)導(dǎo)包及配置文件,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10
  • 解決JDK8的ParallelStream遍歷無序的問題

    解決JDK8的ParallelStream遍歷無序的問題

    這篇文章主要介紹了解決JDK8的ParallelStream遍歷無序的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07

最新評(píng)論