Java併發編程---基礎知識

Java併發編程—基礎知識

什麼是線程

線程是操作系統調度的最小單元,多個線程同時執行,能夠提高程序的性能。

線程和進程

現代操作系統在運行一個程序時,會爲其啓動一個進程;在一個進程裏可以創建多個線程,這些線程都擁有各自的線程私有內存:程序計數器、Java虛擬機棧、本地方法棧,且可以訪問線程共享內存的共享變量。一個程序進程中多個線程的切換(或多核並行)執行,提高了程序的性能。

線程的狀態和切換

Java線程在運行時的生命週期中有6種狀態進行切換,在同一時刻,線程只能處於其中一個狀態:

  1. NEW:新建狀態,通過某種方式創建的,還未啓動(調用Thread.start()方法進行啓動)的線程處於該狀態;
  2. RUNNABLE:運行狀態,包括操作系統的 運行中和準備就緒兩種狀態;
  3. BLOCKED:阻塞狀態,在臨界區等待獲取鎖;
  4. WAITING:無限時等待狀態,等待其他線程顯示喚醒,使用Object.wait()等方法進入該狀態,在該狀態下不會被分配CPU時間片;
  5. TIME_WAITING:超時等待狀態,同WAITING狀態,不過可以在指定時間內自我喚醒,使用Object.wait(long ms)等方法進入該狀態;
  6. TERMINATED:終止狀態,線程已經結束執行。

線程的創建

Java中有多種方法可以實現線程的創建:

  1. 繼承Thread類;
public class ThreadCreate1 extends Thread{
    private int count;

    @Override
    public void run() {
        super.run();
        System.out.println("實現了Thread的線程,這裏是線程具體實現");
    }
}
  1. 實現Runable接口;
public class ThreadCreate2 implements Runnable{
    private int count;
    @Override
    public void run() {
        System.out.println("繼承了Runnable的線程,這裏是線程具體實現");
    }
}
  1. 實現Callable接口;
public class ThreadCreate3 implements Callable<String> {
    private int count;

    @Override
    public String call() throws Exception {
        System.out.println("繼承了Callable的線程,這裏是線程具體實現");
        return "Callable";
    }
}
  1. 繼承FutureTask類。
ppublic class ThreadCreate4 extends FutureTask<String> {
    private int count;
    //演示用,實際中不會這麼寫
    public ThreadCreate4() {
        super(() -> {
            System.out.println("實現了FutureTask的線程,這裏是線程具體實現");
            return "Callable";
        });
    }
}

演示類:

public static void main(String[] args) {
    ExecutorService executor = Executors.newFixedThreadPool(10);
    ThreadCreate1 threadCreate1 = new ThreadCreate1();
    ThreadCreate2 threadCreate2 = new ThreadCreate2();
    ThreadCreate3 threadCreate3 = new ThreadCreate3();
    ThreadCreate4 threadCreate4 = new ThreadCreate4();
  
     threadCreate1.start();
     new Thread(threadCreate2).start();
  
     Future<String> future3 = executor.submit(threadCreate3);
     Future<?> future4 = executor.submit(threadCreate4);
    
    }

結果:

實現了Thread的線程,這裏是線程具體實現
繼承了Runnable的線程,這裏是線程具體實現
繼承了Callable的線程,這裏是線程具體實現
實現了FutureTask的線程,這裏是線程具體實現

線程的啓動和執行

線程類(Thread)有兩個方法 :

  1. start():啓動,該方法將線程加入線程組,由新建狀態(NEW)轉變爲可執行狀態(RUNABLE),當線程分配到CPU時間片時,會執行該線程的run()方法;新建線程必須調用該方法,才能被CPU執行run()方法;或者是加入線程池框架,由框架進行執行。
  2. run():執行,線程運行時方法,Thread.run()方法實際上是調用Runnable的run()方法。

線程的結束

當線程執行完成或者被中斷時,會轉變成終止狀態(TERMINATED)。

線程的中斷

Java的中斷機制是一種線程協作機制,一個線程調用另一個線程的中斷方法,並不能使該線程立即結束,即並不能使用中斷直接終止一個線程。
每個線程對象裏都有一個Boolean類型的中斷標識,可以調用指定的方法來設置該標誌位,線程在合適的時機通過判斷中斷標識位來處理中斷請求,當然也可以選擇不處理。

線程關於中斷的方法如下:

  1. public static boolean interrupted():靜態方法,檢測當前線程是否已經中斷,且清除中斷標識位,即將Boolean值設爲false;
  2. public boolean isInterrupted():檢測當前線程是否已經中斷,和上面的方法一樣都是調用native方法private native boolean isInterrupted(boolean ClearInterrupted),只不過上面的方法傳入true,本方法傳入false;
  3. public void interrupt():中斷線程,將中斷標識位設爲true的唯一方法。

響應中斷的方式:

  1. 拋出InterruptedException異常,交由調用棧上層處理;
  2. 屏蔽中斷請求,不做處理;
  3. 響應中斷,在任務處理前判斷中斷標識位,提前結束任務。

何時使用中斷:

  1. 點擊某個桌面應用中的取消按鈕時;
  2. 某個操作超過了一定的執行時間限制需要中止時;
  3. 多個線程做相同的事情,只要一個線程成功其它線程都可以取消時;
  4. 一組線程中的一個或多個出現錯誤導致整組都無法繼續時;
  5. 當一個應用或服務需要停止時。

守護線程(Daemon線程)

守護線程是一致支持型線程,主要被用作程序中後臺調度和支持下工作。
當JVM中不存在非Daemon線程時,虛擬機會退出。
通過Thread.setDaemon(boolean)方法將線程設置爲守護線程。

線程安全和線程不安全

線程安全性:當多個線程訪問某個類時,這個類始終都能表現出正確的行爲,那麼這個類就是線程安全的,否則即爲線程不安全

線程的安全問題都是由全局變量或靜態變量引起的,即對共享變量的訪問(讀或者寫)會引起線程安全問題。
以下幾種情況是線程安全的:

  1. 無狀態的方法,即方法不訪問共享變量,只使用方法棧的局部變量;
  2. 正確加鎖同步的方法;
  3. 無鎖同步的方法,亦作非阻塞同步。

多線程優缺點

多線程:

將任務分解成多個線程交由單核CPU分時處理,或多核CPU並行處理。

優點:

  1. 提高多核CPU的利用率:如果單線程執行,那麼在一個CPU運行時,其他CPU乾瞪眼不會做出一絲貢獻;
  2. 提高應用處理性能;

缺點:

  1. 增加了上下文切換的開銷:從任務的保存到再加載就是一次上下文切換;
  2. 線程安全性:因爲多線程同時訪問共享變量,如果沒有做好臨界區的同步,會導致結果錯誤,如髒數據等;
  3. 佔用更多的內存空間:每個線程都需要方法棧、程序計數器等堆棧內存。

線程間通信

線程間通信是指:線程之間通過何種機制進行信息的交換。
在命令式編程中,線程間通信方式有兩種:

  1. 共享內存:線程對共享內存進行讀寫;
  2. 消息傳遞

同步: 指程序中用於控制不同線程之間操作發生的相對順序的機制。
​ - 共享內存的通信機制中,程序必須顯式地指定某個方法或代碼需要在線程之間進行互斥訪問,即顯式同步;
​ - 消息傳遞的通信機制中,由於消息的發送在接收之前,所以是隱式同步

Java採用共享內存的併發通信模型,整個通信過程對程序員完全透明。

線程相關的常用方法

  1. Thread.currentThread(): 獲取當前正在執行的線程;
  2. Thread.sleep(long):使調用該方法的線程休眠指定的時間,繼續持有原有的鎖;
  3. threadA.join():調用該方法的線程等待threadA線程的結束;必須在線程啓動後調用纔有效,因爲該方法需要判斷threadA線程isActive();
    實例:
     public static void main(String[] args) {
        Thread test1 = new Thread(() -> System.out.println("test1線程執行完畢"));
        test1.start();
        Thread test2 = new Thread(() -> {
            try {
                test1.join();//線程2等待線程1執行結束
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("test2線程執行完畢");
        });
   
        test2.start();
   
        try {
            test2.join();//主線程等待線程2執行結束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main線程執行完畢");
    
    }

結果:
test1線程執行完畢
test2線程執行完畢
main線程執行完畢

  1. Object.wait()/wait(long):使調用該方法的線程進入WAITING狀態,進入該對象的等待集合,並釋放該對象的鎖;只有當該對象的notify()/notifyAll()方法被調用或線程被中斷纔會返回,從等待隊列中移除;
  2. Objetc.notify():通知一個在該對象上等待的線程(任意選擇,具體方式由是實現者決定),從wait()方法上返回,參與對象鎖的競爭;只有競爭到了該鎖才能真正返回,並該對象鎖;只有持有該對象鎖的線程(有且只有一個)才能調用該方法;
  3. Object.notifyAll():通知該對象上等待的所有線程進行以上操作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章