【Java多線程與併發】——多線程基礎知識

目錄

一、什麼是多線程

二、多線程創建的幾種方式

1、繼承Thread

2、實現Runnable接口

3、實現Callable接口

4、線程池

三、線程的生命週期

四、Thread中的一些方法

五、如何停止一個線程

1、使用return停止線程:

2、使用interrupt方法中斷線程:

3、拋異常法(推薦用法):

4、在沉睡中停止:

5、爲什麼不使用stop()方法停止線程

六、暫停和恢復線程

七、線程的優先級

 八、什麼是守護線程?


一、什麼是多線程

點擊——》進程與線程的聯繫區別

二、多線程創建的幾種方式

1、繼承Thread

thread是程序執行的一個線程,Java虛擬機允許一個應用程序多線程同時執行,每個線程都有優先級,優先級高的比優先級低的線程優先執行,每個線程有可能會被標記爲守護線程。

//繼承Thread方式,重寫run()方法
public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is running");

    }

    public static void main( String[] args )
    {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();

        thread1.start();//啓動線程thread1
        thread2.start();//啓動線程thread2
    }
}

執行結果

Thread-1 is running
Thread-0 is running

 

2、實現Runnable接口

/**
 * 實現Runnable接口
 */
public class MyThread1 implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is running");
    }

    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        Thread thread = new Thread(myThread1);
        thread.start();
    }
}

3、實現Callable接口

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//實現Callable<V>接口,V爲方法返回類型
public class CallableImpl implements Callable<Double> {

    @Override
    public Double call() throws Exception {
        Thread.sleep(1000);
        double result = Math.random();
        System.out.println(Thread.currentThread().getName()+" is running !");
        return result;
    }
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Double> callable = new CallableImpl();
        FutureTask<Double> future = new FutureTask<>(callable);
        //創建並啓動線程
        new Thread(future).start();
        //調用get()會阻塞主線程,否則不會阻塞
        Double result = future.get();
        System.out.println("result=" + result);
        System.out.println("main is running !");
    }
}

 

4、線程池

public class ExecutorThread {
    public static void main(String[] args) {
        
        //使用 guava 開源框架的 ThreadFactoryBuilder 給線程池的線程設置名字
        ThreadFactory nameThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-%d").build();

        ExecutorService service = new ThreadPoolExecutor(4,10,0L,
                                                         TimeUnit.MILLISECONDS,
                                                         new LinkedBlockingDeque<>(1024),
                                                         nameThreadFactory,
                                                         new ThreadPoolExecutor.AbortPolicy());
        service.execute(()-> System.out.println(Thread.currentThread().getName() + " is running !"));
        service.execute(()-> System.out.println(Thread.currentThread().getName() + " is running !"));
        service.execute(()-> System.out.println(Thread.currentThread().getName() + " is running !"));
        service.execute(()-> System.out.println(Thread.currentThread().getName() + " is running !"));
        service.execute(()-> System.out.println(Thread.currentThread().getName() + " is running !"));
        service.shutdown();
    }
}

三、線程的生命週期

線程的五種基本狀態:

1)新建狀態(New):當線程對象創建後,線程進入新建狀態,如:Thread  t  = new Thread();

2)就緒狀態(Runnable):當調用線程對象的start()方法,即t.start(),線程就從新建狀態變爲就緒狀態。處於就緒狀態的線程只是說線程已經做好了準備,隨時等待CPU調度執行,因此調用start()方法並不是說線程立即去執行,只是線程進入了一個可運行的狀態。

3)運行狀態(Running):當處於就緒狀態的線程獲得了CPU的調度執行後,此時線程進入運行狀態。

4)阻塞狀態(Blocked):處於運行狀態中的線程由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分爲三種:

1.等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;

2.同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因爲鎖被其它線程所佔用),它會進入同步阻塞狀態;

3.其他阻塞 -- 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。

5)死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。

四、Thread中的一些方法

  • currentThread():返回正在執行此方法代碼的線程信息
  • isAlive():判斷當前的線程是否處於活動狀態,所謂活動就是指線程已經啓動但是尚未終止。線程處於正在運行或者準備開始運行的狀態。
  • sleep():讓正在執行的線程暫停執行
  • getId():返回當前線程的唯一標識
    /*
     * Thread ID
     * 線程id,在調用init初始化方法的時候,調用nextThreadID()方法生成ID
     */
    private long tid;
    public long getId() {
        return tid;
    }
    /**
     *同步生成線程ID方法
     */
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }
  • getName():獲得線程名稱

線程名稱類似“Thread-0”名稱的由來:由線程的構造方法構造而來,如下圖源碼:

    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }


    /* For autonumbering anonymous threads. 
     * 自動編號的匿名線程
     */
    private static int threadInitNumber;


    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }
  • yield():放棄當前的CPU資源,讓給其他的線程去佔用CPU執行時間。

因爲放棄的時間不確定,有可能剛剛放棄,馬上又獲得CPU時間片。

  • isDaemon():判斷線程是否是守護線程

五、如何停止一個線程

1、使用return停止線程:

public class Main extends Thread {
    @Override
    public void run() {
        while (true){
            if (this.isInterrupted()){
                System.out.println("線程停止了!");
                //使用return來停止線程
                return;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Main main = new Main();
        main.start();
        Thread.sleep(2000);
        main.interrupt();
    }
}

 

2、使用interrupt方法中斷線程:

在Thread.java類裏提供了兩種方法判斷線程是否爲停止的。

this.interrupted():測試當前線程是否已經中斷(靜態方法)。如果連續調用該方法,則第二次調用將返回false。在api文檔中說明interrupted()方法具有清除狀態的功能。執行後具有將狀態標識清除爲false的功能。

this.isInterrupted():測試線程是否已經中斷,但是不能清除狀態標識。

3、拋異常法(推薦用法):

public class MyThread4 extends Thread {
    @Override
    public void run() {
        super.run();
        try {
            for (int i = 0; i < 50000; i++) {
                if (this.isInterrupted()) {
                    System.out.println( "線程已經結束,我要退出" );
//                    return;
                    throw new InterruptedException();
                }
                System.out.println( "i=" + (i + 1) );
            }
            System.out.println( "我是for下面的語句,我被執行說明線程沒有真正結束" );
        } catch (InterruptedException e) {
            System.out.println( "進入MyThread.java類中run方法的catch異常了" );
            e.printStackTrace();
        }
    }
}

 

4、在沉睡中停止:

  @Override
  public void run() {
      super.run();
      try {
          System.out.println( "begin run" );
          Thread.sleep( 500 );
          System.out.println( "begin end" );
      } catch (InterruptedException e) {
          System.out.println("在沉睡中終止");
          e.printStackTrace();
      }
  }
  public static void main(String[] args) {
        try {
            MyThread5 thread5 = new MyThread5();
            thread5.start();
            Thread.sleep( 20 );
            thread5.interrupt();
        } catch (InterruptedException e) {
            System.out.println( "main catch" );
            e.printStackTrace();
        }
    }

運行結果:

從打印結果看,sleep狀態下停止某一個線程,會進入catch語句,並清除狀態值,變成false

5、爲什麼不使用stop()方法停止線程

方法stop()已經棄用,因爲如果強制讓線程終止則有可能使一些請理性的工作得不到完成。另外,對鎖的對象“解鎖”,會導致數據得不到同步的處理,出現了數據不一致的情況。

六、暫停和恢復線程

suspend()——暫停線程

resume()——恢復線程

suspend()和resume()方法缺點——獨佔、不同步

七、線程的優先級

在操作系統中,線程可以劃分優先級,優先級較高的線程得到的CPU資源較多,也就是CPU優先執行優先級較高的線程對象中的任務。

在Java中線程的優先級劃分爲1~10這10個等級。如果小於1或者大於10,則JDK拋出異常throw new IllegalArgumentException()。

JDK中使用了3個常量來預置定義優先級:

public final static int MIN_PRIORITY = 1;

public final static int MIN_PRIORITY = 5;

public final static int MIN_PRIORITY = 10;

線程優先級的特性:

1)線程的優先級具有繼承特性,比如A線程啓動B線程,則A線程與B線程的優先級是一樣的

2)線程優先級具有規則性,CPU儘量將資源讓給優先級比較高的線程

3)線程的優先級具有隨機性,換句話說優先級較高的線程不一定每一次都先執行完

 八、什麼是守護線程?

在Java中線程分爲兩種:用戶線程和守護線程

什麼是守護線程?守護線程是一種特殊的線程。它的作用是爲其他線程的運行提供便利服務,最典型的應用就是GC

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