2020-多線程筆記總結

JAVA 線程實現/建立方式

繼承 Thread 類

Thread 類本質上是實現了 Runnable 接口的一個實例,表明一個線程的實例。 啓動線程的惟一方法就是經過 Thread 類的 start()實例方法。 start()方法是一個 native 方法,它將啓動一個新線程,並執行 run()方法。數據庫

public class MyThread extends Thread {
    public void run() {
    System.out.println("MyThread.run()");
    }
}
MyThread myThread1 = new MyThread();
myThread1.start();
實現 Runnable 接口

若是本身的類已經 extends 另外一個類,就沒法直接 extends Thread,此時,能夠實現一個Runnable 接口。編程

public class MyThread extends OtherClass implements Runnable {
    public void run() {
    System.out.println("MyThread.run()");
    }
}
實現callbale

有返回值的任務必須實現 Callable 接口,相似的,無返回值的任務必須 Runnable 接口。執行Callable 任務後,能夠獲取一個 Future 的對象,在該對象上調用 get 就能夠獲取到 Callable 任務返回的 Object 了。多線程

public class callableTest {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        //建立一個Callable,3秒後返回String類型
        Callable myCallable = new Callable() {
            @Override
            public String call() throws Exception {
//                Thread.sleep(3000);
                System.out.println("calld方法執行了");
                return "call方法返回值";
            }
        };
        List<Future> list = new ArrayList<Future>();
        for (int i = 0; i < 10; i++) {
            Future future = executor.submit(myCallable);
            list.add(future);
        }

線程的狀態

image

線程生命週期

當線程被建立並啓動之後,它既不是一啓動就進入了執行狀態,也不是一直處於執行狀態。
在線程的生命週期中,它要通過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5 種狀態。尤爲是當線程啓動之後,它不可能一直"霸佔"着 CPU 獨自運行,因此 CPU 須要在多條線程之間切換,因而線程狀態也會屢次在運行、阻塞之間切換。jvm

新建狀態(NEW)

當程序使用 new 關鍵字建立了一個線程以後,該線程就處於新建狀態,此時僅由 JVM 爲其分配內存,並初始化其成員變量的值。ide

就緒狀態(RUNNABLE)

當線程對象調用了 start()方法以後,該線程處於就緒狀態。 Java 虛擬機會爲其建立方法調用棧和程序計數器,等待調度運行。工具

運行狀態(RUNNING)

若是處於就緒狀態的線程得到了 CPU,開始執行 run()方法的線程執行體,則該線程處於運行狀態。this

阻塞狀態(BLOCKED)

阻塞狀態是指線程由於某種緣由放棄了 cpu 使用權,也即讓出了 cpu timeslice,暫時中止運行。spa

直到線程進入可運行(runnable)狀態,纔有機會再次得到 cpu timeslice 轉到運行(running)狀態。阻塞的狀況分三種:線程

等待阻塞(o.wait->等待對列) :

運行(running)的線程執行 o.wait()方法, JVM 會把該線程放入等待隊列(waitting queue)中。code

同步阻塞(lock->鎖池)

運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則 JVM 會把該線程放入鎖池(lock pool)中。

其餘阻塞(sleep/join)

運行(running)的線程執行 Thread.sleep(long ms)或 t.join()方法,或者發出了 I/O 請求時,JVM 會把該線程置爲阻塞狀態。當 sleep()狀態超時、 join()等待線程終止或者超時、或者 I/O處理完畢時,線程從新轉入可運行(runnable)狀態。

線程死亡(DEAD)

線程會如下面三種方式結束,結束後就是死亡狀態。

1.正常結束
run()或 call()方法執行完成,線程正常結束。

2.異常結束
線程拋出一個未捕獲的 Exception 或 Error。

3.調用 stop
直接調用該線程的 stop()方法來結束該線程—該方法一般容易致使死鎖,不推薦使用。

鎖的升級

若是有一個對象在被多個線程同時競爭,那麼判斷對象是否有鎖,若是有鎖,那麼會先支持偏向鎖,就是說當前已經得到鎖的線程會優先拿到鎖(markword區記錄了偏向線程id)。那麼拿不到鎖的線程,就會升級鎖,變成CAS synchronized樂觀鎖,會進行一段時間的循環自旋不斷嘗試獲取鎖,當自旋到必定次數後,會再次升級成synchronized重量級鎖。

synchronized

鎖方法會鎖住this,鎖靜態方法會鎖住class對象.鎖代碼塊能夠指定任意對象做爲鎖.

同步代碼塊可能會涉及到一個重入過程,synchronized不會說由於重入去不斷重複獲取鎖釋放鎖的過程,而是用mointer每次重入去作一個計數器加一操做,在釋放鎖的過程當中也會逐步將計算器清零。而後讓其餘線程從block阻塞狀態變成runnable狀態去競爭這個鎖。

synchronized和reentranLock的區別

synchronized不用手動編程,他是一個jvm關鍵字,我也不用關心他鎖釋放的一個過程,直接用就好了,而reentrantlock他是一個類,須要手動lock,配合try catch finally中去作一個鎖釋放操做

線程池

線程和數據庫鏈接這些資源都是很是寶貴的資源。那麼每次須要的時候建立,不須要的時候銷燬,是很是浪費資源的。Java 裏面線程池的頂級接口是 Executor,可是嚴格意義上講 Executor 並非一個線程池,而只是一個執行線程的工具。真正的線程池接口是 ExecutorService。

一個線程池創建後,若是沒有預加載任務,他一開始的核心線程數爲0,當一個新任務被提交時,會創建一個核心線程去執行任務,若是一直來任務,而先前創建的核心線程都在忙,那麼就會一直創建核心線程直到到達最大核心線程數。

但核心線程數最大,並且都在執行任務時,後來的任務會被放到blockingqueue(阻塞隊列裏),若是阻塞隊列也滿了,就會去創建新線程,此時的線程叫非核心線程,當整個線程池的線程數達到最大,他也有一個max access時,會觸發拒絕策略。

拒絕策略

AbortPolicy 停止策略

會直接拋出異常來執行停止任務執行拒絕

DiscardPolicy 拋棄策略

他會丟棄不執行多餘的任務來執行拒絕

DIscardOldestPolicy

會丟棄最先你未執行的任務

callrunpolicy

公平鎖和非公平鎖的區別

在多線程環境下

  1. 公平鎖通常指代 先到達臨界區的線程必定比後到臨界區的線程 優先拿到鎖
  2. 非公平鎖則指代 先到達臨界區的線程也不必定比後到臨界區的線程 優先拿到鎖

image

相關文章
相關標籤/搜索