溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

發(fā)布時(shí)間:2021-06-17 13:52:14 來源:億速云 閱讀:203 作者:Leah 欄目:編程語言

今天就跟大家聊聊有關(guān)Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

1.Thread的構(gòu)造方法

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

package threadAPI;
 
public class CreateThread {
  public static void main(String[] args) {
    Thread t1 = new Thread();
    Thread t2 = new Thread();
 
    t1.start();
    t2.start();
 
    System.out.println(t1.getName());
    System.out.println(t2.getName());
 
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

總結(jié)1:

創(chuàng)建線程對象Thread,默認(rèn)有一個(gè)線程名,以Thread-開頭,從0開始計(jì)數(shù)

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Thread-0

Thread-1

Thread-2

可以看到Thread()中默認(rèn)傳入的第二個(gè)參數(shù),即Runnable接口為null

在init方法中,Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程會將我們傳入的target給Thread的成員變量Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

然后在調(diào)用run方法的時(shí)候,會做如下判斷

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

所以當(dāng)target為null的時(shí)候,默認(rèn)的run方法中什么也不做

總結(jié)2:

如果在構(gòu)造Thread的時(shí)候,沒有傳遞Runnable接口或者沒有復(fù)寫Thread的run方法,該Thread將不會調(diào)用任何東西

如果傳遞了Runnable接口的實(shí)例,則會執(zhí)行該方法的邏輯代碼

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

如果復(fù)寫了Thread的run方法,則會執(zhí)行復(fù)寫的邏輯代碼

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

為線程傳遞一個(gè)線程名

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

這時(shí)我們傳入的參數(shù)名,會傳遞給線程對象的成員變量name

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

為線程傳遞線程名的同時(shí),傳遞Runnbale接口的實(shí)現(xiàn)類對象,調(diào)用原理同上

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

我們還可以在為線程傳入線程組

其實(shí)在上述的方法中沒有傳入線程組的情況下,init方法的ThreadGroup默認(rèn)被傳入null

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

parent即調(diào)用Thread對象的start方法的線程

package threadAPI;
 
public class CreateThread {
  public static void main(String[] args) {
    Thread t = new Thread();
    t.start();
 
    System.out.println(t.getThreadGroup());
    System.out.println(Thread.currentThread().getName());
    System.out.println(Thread.currentThread().getThreadGroup());
 
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

總結(jié):

如果構(gòu)造線程對象時(shí)未傳入ThreadGroup,Thread默認(rèn)會獲取父線程的ThreadGroup作為該線程的ThreadGroup,此時(shí)子線程和父線程在同一個(gè)ThreadGroup中

我們可以查看當(dāng)前ThreadGroup中有多少個(gè)線程在運(yùn)行

package threadAPI;
 
public class CreateThread {
  public static void main(String[] args) {
    Thread t = new Thread();
    t.start();
 
    ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
    System.out.println(threadGroup.activeCount());
    
    //創(chuàng)建一個(gè)Thread數(shù)組
    Thread[] threads = new Thread[threadGroup.activeCount()];
    //將threadGroup中的數(shù)組枚舉到threads數(shù)組中
    threadGroup.enumerate(threads);
 
    //打印threads接收到的線程
    for (Thread thread : threads) {
      System.out.println(thread);
    }
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

線程組的詳細(xì)介紹見后續(xù)博文:

stackSize:

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

演示代碼:

public class CreateThreadDemo {
 
  private static int counter;
 
  public static void main(String[] args) {
    Thread t1 = new Thread(null, new Runnable() {
      @Override
      public void run() {
        try {
          add(1);
        }catch (Error e){
          System.out.println(counter);
          e.printStackTrace();
        }
      }
 
      private void add(int i){
        counter++;
        add(i+1);
      }
 
    },"",1<<24);
 
    t1.start();
  }
}

運(yùn)行結(jié)果:

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

將stackSize修改:

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程修改為Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

運(yùn)行結(jié)果:

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

總結(jié):

構(gòu)造Thread的時(shí)候傳入stacksize代表著該線程占用虛擬機(jī)棧的大?。ㄌ摂M機(jī)棧本身的大小在程序運(yùn)行時(shí)就已經(jīng)確定),如果沒有指定stacksize的大小,默認(rèn)是0,0代表著會忽略該參數(shù),該參數(shù)會被JNI函數(shù)去使用

需要注意的是:該參數(shù)有一些平臺有效,在有些平臺則無效2.start方法

2、調(diào)用start

方法,會執(zhí)行run方法

但不能調(diào)用start方法兩次,會拋出異常

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

也可以直接調(diào)用Thread的run方法,但不會啟動另一個(gè)線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

當(dāng)你第一次調(diào)用線程的start方法時(shí)候,會返回兩個(gè)線程,一個(gè)是調(diào)用線程,一個(gè)是新創(chuàng)建的執(zhí)行run方法的線程

start方法的源碼實(shí)現(xiàn)使用了模板方法,以下是模擬它的實(shí)現(xiàn)技巧:

public class TemplateMethod {
  //此處final,是因?yàn)閟tart方法的邏輯是固定的,不允許子類重寫此方法
  public final void start(String message)
  {
    System.out.println("################");
    run(message);
    System.out.println("################");
  }
 
  protected void run(String message)
  {
 
  }
 
  public static void main(String[] args) {
    TemplateMethod t1 = new TemplateMethod() {
      @Override
      protected void run(String message) {
        System.out.println("*"+message+"*");
      }
    };
 
    t1.start("hello,world");
 
    TemplateMethod t2 = new TemplateMethod() {
      @Override
      protected void run(String message) {
        System.out.println("+"+message+"+");
      }
    };
 
    t2.start("hello,world");
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

上述代碼使用模板方法的大致思想是定義一個(gè)模板方法,它其中一些代碼已經(jīng)實(shí)現(xiàn),而另一些需要交給用戶去上實(shí)現(xiàn),該方法確定結(jié)構(gòu),對外提供統(tǒng)一的調(diào)用接口start,定義另一個(gè)方法,提供給用戶繼承,用戶可以重寫,也可以不重寫run方法,即不變的部分用模板實(shí)現(xiàn),變化的部分提取出來,交給用戶繼承重寫

3.setDaemon

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中的線程分為兩種:

  • 用戶線程(setDaemo(false))

  • 守護(hù)線程(setDaemo(true))

什么是守護(hù)線程?

專門用于服務(wù)其他的線程,如果其他的線程(即用戶線程(包括main線程))都執(zhí)行完畢,JVM中只剩下守護(hù)線程時(shí),此時(shí)JVM不管守護(hù)線程是否執(zhí)行完畢,都會結(jié)束執(zhí)行

示例代碼1:

public class DaemonDemo {
  public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
      @Override
      public void run() {
        try {
          System.out.println(Thread.currentThread().getName()+"\trun");
          Thread.sleep(10*1000);
          System.out.println(Thread.currentThread().getName()+"\tout");
        }catch (Exception e){
          e.printStackTrace();
        }
      }
    };
 
    t.setDaemon(true);
    t.start();
 
    Thread.sleep(5*1000);
 
    System.out.println(Thread.currentThread().getName());
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

當(dāng)main線程執(zhí)行結(jié)束時(shí),運(yùn)行的唯一線程是守護(hù)線程,JVM退出,并未執(zhí)行此句

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

疑問:在我們創(chuàng)建的t線程中創(chuàng)建一個(gè)守護(hù)線程t1,當(dāng)t線程結(jié)束,主線程未結(jié)束的時(shí)候,守護(hù)線程是否結(jié)束呢?

示例代碼2:

/**
 * t1、t3、main是用戶線程
 * t2、t4是守護(hù)線程
 *
 * t1執(zhí)行1秒結(jié)束
 * main執(zhí)行5秒結(jié)束
 * t3執(zhí)行8秒結(jié)束
 *
 * t2、t4正常執(zhí)行完需要10秒
 */
public class DaemonDemo {
  public static void main(String[] args) throws InterruptedException {
 
    //t1線程睡眠1秒就執(zhí)行結(jié)束
    Thread t1 = new Thread(){
      @Override
      public void run() {
 
        //t2線程是守護(hù)線程,如果像用戶線程一樣,必須執(zhí)行10秒才執(zhí)行結(jié)束
        Thread t2 = new Thread(()-> {
          try {
            for (int i = 1; i <= 10; i++) {
              Thread.sleep(1*1000);
              System.out.println("t2守護(hù)線程執(zhí)行:"+i);
            }
 
 
            System.out.println("t2守護(hù)線程結(jié)束");
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        });
 
        t2.setDaemon(true);
        t2.start();
 
 
        try {
          Thread.sleep(1*1000);
          System.out.println("t1線程結(jié)束");
        }catch (Exception e){
          e.printStackTrace();
        }
      }
    };
 
    t1.start();
 
 
    //t3線程執(zhí)行8秒執(zhí)行結(jié)束
    Thread t3 = new Thread(()-> {
      try {
        for (int i = 1; i <= 8; i++) {
          Thread.sleep(1*1000);
          System.out.println("t3守護(hù)線程執(zhí)行:"+i);
        }
 
 
        System.out.println("t3守護(hù)線程結(jié)束");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    });
 
    t3.start();
 
 
    //t4線程是守護(hù)線程,如果像用戶線程一樣,必須執(zhí)行10秒才執(zhí)行結(jié)束
    Thread t4 = new Thread(()-> {
      try {
        for (int i = 1; i <= 10; i++) {
          Thread.sleep(1*1000);
          System.out.println("t4守護(hù)線程執(zhí)行:"+i);
        }
 
 
        System.out.println("t4守護(hù)線程結(jié)束");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    });
 
    t4.setDaemon(true);
    t4.start();
 
    //main線程執(zhí)行5秒就執(zhí)行結(jié)束
    Thread.sleep(5*1000);
 
    System.out.println("main線程結(jié)束");
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

總結(jié):只有當(dāng)JVM中的用戶線程(包括main線程)執(zhí)行完的時(shí)候,未執(zhí)行完的守護(hù)線程會隨著JVM退出而被強(qiáng)制退出,不會再執(zhí)行后續(xù)的代碼

示例代碼3:

public class DaemonDemo {
  public static void main(String[] args) throws InterruptedException {
 
    Thread t1 = new Thread(){
      @Override
      public void run() {
 
        try {
          Thread.sleep(1*1000);
          System.out.println("t1線程結(jié)束");
        }catch (Exception e){
          e.printStackTrace();
        }
      }
    };
 
    t1.start();
    t1.setDaemon(true);
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

總結(jié):

setDaemon(true)方法必須在調(diào)用start方法之前調(diào)用,

否則會拋出java.langIllegalThreadStateException異常

4.獲取線程的名稱、id、優(yōu)先級

(1)獲取線程的名稱

1.使用Thread類中的方法getName    

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

2.可以獲得當(dāng)前正在執(zhí)行的線程,使用線程中的方法getName()獲取線程的名稱    

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

示例代碼:

MyThread.java

package Demo01;
 
//1.創(chuàng)建一個(gè)Thread類的子類
public class MyThread extends Thread{
 
 
  //2.在Thread類的子類中重寫Thread類的run方法,設(shè)置線程任務(wù),即線程要干什么
  @Override
  public void run() {
    //第一種方式:直接調(diào)用getName獲取線程名稱
    System.out.println(this.getName());
 
    //第二種方式:獲得當(dāng)前正在執(zhí)行的線程,使用線程中的方法getName()獲取線程的名稱
    //System.out.println(Thread.currentThread().getName());
  }
}

ThreadDemo01.java

package Demo01;
 
public class ThreadDemo01 {
  public static void main(String[] args) {
 
    //3.創(chuàng)建Thread類的子類對象
    MyThread mt=new MyThread();
    //4.調(diào)用Thread類的start方法,開啟線程,執(zhí)行run方法
    mt.start();
 
    new MyThread().start();
    new MyThread().start();
    //獲得當(dāng)前正在執(zhí)行的線程,使用線程中的方法getName()獲取線程的名稱
    System.out.println(Thread.currentThread().getName());
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

(2)獲取id

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

public class IdDemo {
  public static void main(String[] args) throws InterruptedException {
 
    Thread t1 = new Thread(()->{
 
      try {
        Thread.sleep(100*1000);
      }catch (Exception e){
        e.printStackTrace();
      }
 
 
    },"t1");
 
    System.out.println(t1.getName()+"線程的線程id為:"+t1.getId());
    System.out.println("main線程的id為:"+Thread.currentThread().getId());
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

在JVM啟動時(shí),除過main線程,JVM還會啟動的9個(gè)后臺線程,我們的線程id從11開始遞增

(3)獲取線程優(yōu)先級

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

說明:高優(yōu)先級的線程要搶占低優(yōu)先級線程CPU的執(zhí)行權(quán),但是只是從概率上講,高優(yōu)先級的線程高概率的情況下被執(zhí)行,并不意味著只有當(dāng)高優(yōu)先級的線程執(zhí)行完以后,低優(yōu)先級的線程才執(zhí)行

通過優(yōu)先級可以企圖改變線程執(zhí)行的優(yōu)先順序,但是不一定會按照我們定義的順序去執(zhí)行,所以不要通過線程優(yōu)先級去控制先去執(zhí)行哪個(gè)線程再去執(zhí)行哪個(gè)線程

示例代碼:

public class PriorityDemo {
  public static void main(String[] args) throws InterruptedException {
 
    Thread t1 = new Thread(()->{
      for (int i = 0; i < 1000; i++) {
        System.out.println(Thread.currentThread().getName()+"-Index"+i);
      }
    },"t1");
 
    t1.setPriority(Thread.MAX_PRIORITY);
 
    Thread t2 = new Thread(()->{
      for (int i = 0; i < 1000; i++) {
        System.out.println(Thread.currentThread().getName()+"-Index"+i);
      }
    },"t2");
    t2.setPriority(Thread.NORM_PRIORITY);
 
    Thread t3 = new Thread(()->{
      for (int i = 0; i < 1000; i++) {
        System.out.println(Thread.currentThread().getName()+"-Index"+i);
      }
    },"t3");
    t3.setPriority(Thread.MIN_PRIORITY);
 
    t1.start();
    t2.start();
    t3.start();
 
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

可以看到還是可以看到它們的交替執(zhí)行,并非一定按照我們賦予的優(yōu)先級順序來執(zhí)行

5.join

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

含義:調(diào)用join方法的線程等待執(zhí)行join方法的線程執(zhí)行結(jié)束(下面的方法是執(zhí)行join方法的線程固定時(shí)間,然后再繼續(xù)與存活的其他線程一起交替執(zhí)行下面的代碼)

示例代碼1:

當(dāng)沒有join之前

import java.util.stream.IntStream;
 
public class ThreadJoin {
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(()->{
      IntStream.range(1,10)
          .forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));
    },"t1");
 
    t1.start();
 
    IntStream.range(1,10)
        .forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));
 
 
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

main線程和t1線程交替執(zhí)行

當(dāng)在main線程中調(diào)用t1線程的join方法時(shí)

import java.util.stream.IntStream;
 
public class ThreadJoin {
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(()->{
      IntStream.range(1,10)
          .forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));
    },"t1");
 
    t1.start();
    t1.join();
 
    IntStream.range(1,10)
        .forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));
 
 
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

main線程要等到t1線程執(zhí)行完,再執(zhí)行它自己的代碼

示例代碼2:

import java.util.stream.IntStream;
 
public class ThreadJoin {
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(()->{
      IntStream.range(1,10)
          .forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));
    },"t1");
 
 
    Thread t2 = new Thread(()->{
      IntStream.range(1,10)
          .forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));
    },"t2");
 
    t1.start();
    t2.start();
    t1.join();
    t2.join();
 
    IntStream.range(1,10)
        .forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));
 
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

在A線程中調(diào)用B線程的join方法,則A等待B線程,與其他線程無關(guān)

上述代碼中,在main線程中調(diào)用了t1和t2的join方法,所以是main線程等t1和t2執(zhí)行完了,才繼續(xù)執(zhí)行下面的代碼,但是t1和t2線程之間沒有仍然交替執(zhí)行

示例代碼3:

import java.util.stream.IntStream;
 
public class ThreadJoin {
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(()->{
      try {
        Thread.sleep(1000);
        IntStream.range(1,10)
            .forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
 
 
    },"t1");
    
    t1.start();
    t1.join(1000);
 
 
    IntStream.range(1,10)
        .forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));
    
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

上述代碼main線程等待了t1線程執(zhí)行了1秒之后,又繼續(xù)與t1線程交替執(zhí)行即并發(fā)執(zhí)行

6.Interrupt

6.1 interrupt()和isInterrupted

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

其作用是中斷此線程(此線程不一定是當(dāng)前線程,而是指調(diào)用該方法的Thread實(shí)例所代表的線程),但實(shí)際上只是給線程設(shè)置一個(gè)中斷標(biāo)志,線程仍會繼續(xù)運(yùn)行。

而當(dāng)調(diào)用wait、sleep、join方法時(shí),就會清除該中斷標(biāo)志,并且拋出InterruptedException異常,我們可以捕獲該異常,然后做一些事情,比如,break跳出循環(huán)繼續(xù)向下執(zhí)行結(jié)束程序

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

只有一個(gè)作用:判斷此線程(此線程不一定是當(dāng)前線程,而是指調(diào)用該方法的Thread實(shí)例所代表的線程)的線程狀態(tài)(即中斷標(biāo)志是否被設(shè)置)

不會清除中斷標(biāo)志

示例代碼1:

public class InterruptDemo {
  public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
      @Override
      public void run() {
        while(true){
          System.out.println("t線程正在執(zhí)行");
          System.out.println(">>"+this.isInterrupted());
          if(this.isInterrupted()){
            break;
          }
        }
 
        System.out.println("只是設(shè)置一個(gè)中斷標(biāo)志");
        System.out.println("中斷標(biāo)志還在否?"+this.isInterrupted());
      }
    };
 
    t.start();
    //簡單進(jìn)行休眠,保證線程t進(jìn)入運(yùn)行狀態(tài)
    Thread.sleep(100);
    System.out.println("main\t"+t.isInterrupted());
    t.interrupt();
    System.out.println("main\t"+t.isInterrupted());
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

結(jié)論:

  • 即使我們調(diào)用了interrupt,它并不會結(jié)束程序,而是設(shè)置一個(gè)中斷標(biāo)志

  • isInterrupted方法不會清除中斷標(biāo)志

示例代碼2:

public class InterruptDemo {
  public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
      @Override
      public void run() {
        while(true){
          try {
            Thread.sleep(10);
          }catch (InterruptedException e){
            System.out.println("收到中斷信號");
            e.printStackTrace();
          }
        }
      }
    };
 
    t.start();
    //簡單進(jìn)行休眠,保證線程t進(jìn)入運(yùn)行狀態(tài)
    Thread.sleep(100);
    System.out.println("main\t"+t.isInterrupted());
    t.interrupt();
    System.out.println("main\t"+t.isInterrupted());
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

此種結(jié)果下程序是按照如下順序被調(diào)度的:

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

此種結(jié)果下程序是按照如下順序被調(diào)度的:

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

結(jié)論:

sleep方法會清除中斷標(biāo)志并拋出InterruptedException異常

示例代碼3:

public class InterruptDemo {
 
  private static final Object MONITTOR = new Object();
 
  public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
      @Override
      public void run() {
        while(true){
 
          synchronized (MONITTOR){
            try {
              MONITTOR.wait(10);
            }catch (InterruptedException e){
              System.out.println("收到中斷信號");
              e.printStackTrace();
            }
          }
 
        }
      }
    };
 
    t.start();
    //簡單進(jìn)行休眠,保證線程t進(jìn)入運(yùn)行狀態(tài)
    Thread.sleep(100);
    System.out.println("main\t"+t.isInterrupted());
    t.interrupt();
    System.out.println("main\t"+t.isInterrupted());
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

結(jié)論:

wait方法會清除中斷標(biāo)志并拋出InterruptedException異常

示例代碼4:

public class InterruptDemo {
 
  public static void main(String[] args) {
    Thread t = new Thread(){
      @Override
      public void run() {
        while(true){
 
        }
      }
    };
 
    t.start();
 
 
    //使用t2線程去執(zhí)行interrupt()線程t
    Thread t2 = new Thread(){
      @Override
      public void run() {
        try {
          Thread.sleep(100);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
 
        t.interrupt();
        System.out.println("interrupt");
      }
    };
 
    t2.start();
 
    try {
      t.join();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

當(dāng)我們調(diào)用了join方法時(shí),使用interrupt方法設(shè)置線程標(biāo)志,我們發(fā)現(xiàn)程序并沒有被打斷,拋出異常,與API描述的不符?

我們反思一下,sleep,wait都是當(dāng)前線程sleep,wait,而這里調(diào)用join的是main線程,即main線程join,而我們中斷的是t線程,所以沒有拋出異常,代碼修改如下:

public class InterruptDemo {
 
  public static void main(String[] args) {
    Thread t = new Thread(){
      @Override
      public void run() {
        while(true){
 
        }
      }
    };
 
    t.start();
 
    Thread main = Thread.currentThread();
    //使用t2線程去執(zhí)行interrupt()線程t
    Thread t2 = new Thread(){
      @Override
      public void run() {
        try {
          Thread.sleep(100);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
 
        main.interrupt();
        System.out.println("interrupt");
      }
    };
 
    t2.start();
 
    try {
      t.join();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

可以看到j(luò)oin的底層還是在調(diào)用wait方法

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

6.2 interrupted()

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

兩個(gè)作用:

判斷當(dāng)前線程狀態(tài)(即中斷標(biāo)志是否被設(shè)置)

清除中斷標(biāo)志(如果已經(jīng)被設(shè)置)

  • 如果線程被interrupt()方法設(shè)置過中斷標(biāo)志,當(dāng)執(zhí)行interrupted()方法就會清除該中斷標(biāo)志并返回true,就相當(dāng)于我們接收到了interrupt()方法傳來的“中斷信號”,我們可以通過true或false這樣的判斷,來做一些事情

  • 如果線程中的中斷標(biāo)志沒有被設(shè)置,它就會返回false

有了isInterrupted()方法,為什么還要interrupted()方法呢?

因?yàn)槿绻覀兪莻鹘oThread一個(gè)Runnabe接口,重寫其中的run方法,我們又想調(diào)用判斷是否有中斷標(biāo)志,我們又無法調(diào)用isInterrupted()方法,所以Thread提供靜態(tài)方法interrupted()供我們使用

并且isInterrupted和interrupted一個(gè)很大區(qū)別就是:interrupted會清除中斷標(biāo)志,而isInterrupted不會

源碼如下:

供我們調(diào)用的isInterrupted:

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

本地方法isInterrupted:

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

供我們調(diào)用的靜態(tài)interrupted:

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

public class InterruptDemo {
 
  public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(new Runnable() {
      @Override
      public void run() {
        {
          while(true){
 
            //此處在Runnable接口中重寫run方法,只能調(diào)用靜態(tài)方法interrupted判斷是否有中斷標(biāo)志
            if(Thread.interrupted())
            {
              System.out.println("收到中斷信號");
              System.out.println("中斷標(biāo)志還在否?"+Thread.interrupted());
              break;
            }
          }
        }
      }
    });
 
    t.start();
    //簡單進(jìn)行休眠,保證線程t進(jìn)入運(yùn)行狀態(tài)
    Thread.sleep(100);
    System.out.println("main\t"+t.isInterrupted());
    t.interrupt();
    System.out.println("main\t"+t.isInterrupted());
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

6.3 如何采用優(yōu)雅的方式結(jié)束線程?

(1)方式1:通過開關(guān)的方式即一個(gè)flag的方式去結(jié)束一個(gè)線程

/**
 * 通過開關(guān)的方式即一個(gè)flag的方式去結(jié)束一個(gè)線程
 */
public class ThreadCloseGraceful {
 
  private static class Worker extends Thread{
    private volatile boolean on = true;
 
    @Override
    public void run() {
      while(on){
 
      }
    }
 
 
    public void shutdown(){
      on = false;
    }
  }
 
  public static void main(String[] args) {
    Worker worker = new Worker();
    worker.start();
 
    try {
      Thread.sleep(10000);
    }catch (InterruptedException e){
      e.printStackTrace();
    }
 
    worker.shutdown();
  }
  
}

(2)方式2:通過中斷結(jié)束一個(gè)線程

/**
 * 通過捕獲中斷異常來結(jié)束程序
 */
public class ThreadCloseGraceful {
 
  private static class Worker extends Thread{
 
 
    @Override
    public void run() {
      while(true){
        try {
          Thread.sleep(1000); //遇到sleep清除中斷狀態(tài),并捕獲拋出的中斷異常
        }catch (InterruptedException e){
          System.out.println("Worker線程捕獲到了中斷異常");
          break;
        }
      }
      //這里還可以實(shí)現(xiàn)一些邏輯的代碼
 
      System.out.println("Worker線程被中斷結(jié)束");
    }
 
  }
 
  public static void main(String[] args) {
    Worker worker = new Worker();
    worker.start();
 
    try {
      Thread.sleep(5000);
    }catch (InterruptedException e){
      e.printStackTrace();
    }
 
    worker.interrupt(); //設(shè)置worker線程的中斷狀態(tài)
    System.out.println("主線程結(jié)束");
  }
 
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

/**
 * 通過線程調(diào)用靜態(tài)方法interrupted來結(jié)束程序
 */
public class ThreadCloseGraceful {
 
  private static class Worker extends Thread{
 
 
    @Override
    public void run() {
      while(true){
        if(Thread.interrupted()) //通過調(diào)用此方法,清除中斷狀態(tài),清除后返回true
          break;
        
      }
      //這里還可以實(shí)現(xiàn)一些邏輯的代碼
 
      System.out.println("Worker線程被中斷結(jié)束");
    }
 
  }
 
  public static void main(String[] args) {
    Worker worker = new Worker();
    worker.start();
 
    try {
      Thread.sleep(5000);
    }catch (InterruptedException e){
      e.printStackTrace();
    }
 
    worker.interrupt(); //設(shè)置中斷狀態(tài)
    System.out.println("主線程結(jié)束");
  }
 
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

(3)方式3:封裝一個(gè)類按照時(shí)間強(qiáng)制結(jié)束一個(gè)線程

分析上述兩種方式不能解決的問題:

當(dāng)我有一個(gè)很耗時(shí)的任務(wù),本來預(yù)期半個(gè)小時(shí)結(jié)束,結(jié)果它運(yùn)行了兩個(gè)小時(shí),我想結(jié)束它,也就是它在一次循環(huán)中一直執(zhí)行或阻塞,它沒有機(jī)會去判斷開關(guān)狀態(tài)或者中斷標(biāo)志狀態(tài)

方式3的代碼:

public class ThreadService {
 
  //定義一個(gè)執(zhí)行線程用于控制守護(hù)線程執(zhí)行我們的業(yè)務(wù)
  private Thread excuteThread;
  //用于判斷線程是否執(zhí)行完,執(zhí)行完的話,關(guān)閉方法就不用再去調(diào)用中斷方法
  private boolean finished = false;
 
  public void excute(Runnable task){
 
 
 
    excuteThread = new Thread(){
      @Override
      public void run() {
        //創(chuàng)建一個(gè)守護(hù)線程
        Thread runner = new Thread(task);
        runner.setDaemon(true);
        runner.start();
        try {
          runner.join();
          finished = true;
        } catch (InterruptedException e) {
 
        }
 
      }
    };
 
    excuteThread.start();
  }
 
  public void shutdown(long mills){
    long currentTime = System.currentTimeMillis();
    while (!finished){
      if(System.currentTimeMillis() - currentTime > mills){
        System.out.println("任務(wù)超時(shí),需要結(jié)束它");
        //我們執(zhí)行任務(wù)的線程由excuteThread線程去join
        // 那么我們設(shè)置中斷標(biāo)志,異常會被捕獲,然后不執(zhí)行任何代碼,直接結(jié)束執(zhí)行線程
        excuteThread.interrupt();
        break;
      }
 
      //業(yè)務(wù)既沒有完成,也沒有到我們設(shè)定的關(guān)閉時(shí)間,短暫的進(jìn)行休眠
      try {
        excuteThread.sleep(1);
      } catch (InterruptedException e) {
        System.out.println("執(zhí)行線程被打斷");
        break;
      }
    }
 
    finished = false;
  }
}
/**
 * 通過守護(hù)線程執(zhí)行業(yè)務(wù),通過一個(gè)用戶線程控制守護(hù)線程
 *
 * 注意:這種方法的前提是JVM中只有用戶線程,
 *    即當(dāng)我們調(diào)用shutdown以后不能再有其他用戶線程還在執(zhí)行,有的話,守護(hù)線程不會被結(jié)束
 */
public class ThreadCloseGraceful {
 
 
  public static void main(String[] args) {
 
    ThreadService service = new ThreadService();
    long start = System.currentTimeMillis();
    service.excute(()->{
 
      //假設(shè)在執(zhí)行一個(gè)很耗時(shí)的任務(wù)
      while(true){
 
        System.out.println("守護(hù)線程執(zhí)行的時(shí)間:"+(System.currentTimeMillis()-start)+"ms");
      }
    });
 
    //等待5000ms結(jié)束該線程
    service.shutdown(5000);
 
    long end = System.currentTimeMillis();
 
    System.out.println(end - start);
 
    /**
     * 此處如果不注釋,則為結(jié)果2
     *
     * 因?yàn)橹袛嘟Y(jié)束了執(zhí)行線程,但是main作為用戶線程并沒有結(jié)束,所以守護(hù)線程并沒有結(jié)束
     */
    /*
    try {
      Thread.sleep(10*1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    */
  }
 
}

運(yùn)行結(jié)果1:守護(hù)線程隨著唯一的excuteThread被結(jié)束而結(jié)束

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

運(yùn)行結(jié)果2:由于main線程在excuteThread結(jié)束后并未執(zhí)行完,守護(hù)線程未結(jié)束

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

所以注意:

這種方法的前提是JVM中只有一個(gè)用戶線程,即當(dāng)我們調(diào)用shutdown以后不能再有其他用戶線程還在執(zhí)行,有的話,守護(hù)線程不會被結(jié)束

7.yield

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

當(dāng)調(diào)用Thread.yield函數(shù)時(shí),會給線程調(diào)度器一個(gè)當(dāng)前線程愿意讓出CPU使用的暗示,只是大概率下會把CPU的執(zhí)行權(quán)交給其他線程,但是不是絕對的,線程調(diào)度器可能會忽略這個(gè)暗示,還可能再次把執(zhí)行權(quán)分配給當(dāng)前線程

yield也不會對鎖的行為有影響

public class YieldDemo {
  public static void main(String[] args) {
    Runnable yieldTask = new Runnable() {
      @Override
      public void run() {
        for (int i = 0; i < 10; i++) {
          System.out.println(Thread.currentThread().getName()+i);
          if(i == 5){
            Thread.yield();
          }
        }
      }
    };
 
    Thread t1 = new Thread(yieldTask,"A");
    Thread t2 = new Thread(yieldTask,"B");
    t1.start();
    t2.start();
  }
}

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程             

Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程

看完上述內(nèi)容,你們對Java中怎么利用ThreadAPI實(shí)現(xiàn)多線程有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI