1. 程式人生 > >線程相關的幾個問題

線程相關的幾個問題

hook break kthread lee dea exception ica exceptio 等待

來自小馬哥公開課

創建線程

如何創建?

歸根到底new Thread()並調用start()是java創建並運行線程的唯一方式. Runnable,Callable 以及繼承 Thread, 還有lamda表達式等類似代碼其實都是線程的使用方式.

相比較而言, 創建進程的Java方式是使用Runtime#exec(String command)方法實現.

如何銷毀?

Java正在執行的線程只有在執行完畢, 或者拋出異常後才能停止. 也就是基本可以認為: 線程一旦開始執行就無法從外部控制其銷毀.

Thread#stop()方法用來停止線程, 但此方法過於暴力, 可能會導致資源不會正確釋放, 會導致死鎖, 此方法已經標記為過期.

while(true)循環類線程可以使用interrupt()方法通過拋出InterruptedException來結束線程, 但要註意只有線程阻塞情況下才會拋出此異常, 因此, 對於非阻塞狀態的線程, 是使用isInterrupted()方法來判斷中斷標誌來結束控制循環.

while(!isInterrupted()){
    try{
        Thread.sleep(5000);
    }catch(InterruptedException e){
        e.pringStackTrace();
        break;
    }
}

如何停止?

使用狀態標誌變量停止方式如下:

class TaskThread implements Runnable{
    private volatile boolean stoped = fasle; // 標識字段, 註意線程安全問題, 使用volatile保證可見性

    @Override
    public void run(){
        if(!stopped){
            // todo
        }
    }

    public void setStoped(boolean stop){
        stoped=stop;
    }
}

控制線程的執行順序

使用join()

join()方法會阻塞調用它的線程. 方法的關鍵代碼如下:

public final synchronized void join(long millis){
    while (isAlive()) {
        wait(millis);
    }
}

可見其最終是使用Object#wait(long millis)來實現, 也就是說, 我們自己也可以使用Object#wait()來控制線程的執行順序.

另外的方法

  • 使用單線程池的方式
ExecutorService singlePool = Executors.newSingleThreadExecutor()
singlePool.submit(t1);
singlePool.submit(t2);
singlePool.shutdown();
  • 使用CountdownLatch
  • 使用循環檢測線程
t1.start()
while(t1.isAlive()){
    //Thread.sleep(5);
}

t2.start();
  • 使用Thread.sleep(long millis)

線程異常

Java只能捕獲unchecked異常. 線程異常時線程即終止.

如何捕獲?

可以使用下面方式捕獲線程拋出的異常, 這樣做的話線程的異常堆棧將不會輸出到System.error標準輸出流.

對於堆棧過多的場景來自定義錯誤handler處理, 可以防止錯誤堆棧導致內存耗盡. Spring boot 中的SpringApplication有相關的接口.

Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh);

線程池中的異常處理

除了上面的方法, 還可以使用如下方法捕獲異常.

ThreadPoolExecutor#afterExecute(Runnable r, Throwable t)

線程狀態

NEW 新建線程 new
RUNABLE 可運行, start()
BLOCKED 阻塞, 獲取鎖
WAITING 等待
TIME_WAITING 超時等待
TERMINATED 終結

如何獲取 JVM 中所有線程狀態

  • 使用外部工具: jstack pid 可以顯示出所有線程.
  • 代碼使用JMX:
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] ThreadIds = threadMXBean.getAllThreadIds();
for(long threadId : ThreadIds){
    ThreadInfo threadInfo =ThreadMXBean.getThreadInfo(threadId);
}

線程同步

synchronized 在方法和代碼塊上的區別,可以查看字節碼文件. 關註monitorenter和monitorexit指令, 方法的話關註其flag的標誌位.

synchronized 和 ReentrantLock 的區別

二者均可重入. ReentrantLock 多了三個功能, 可中斷, 公平鎖, 綁定多個Condition. ReentrantLock 性能可能更高一點, 也有更多的控制, 比如嘗試獲得鎖, 釋放等, 同時也可以綁定多個條件.

偏向鎖在 ReentrantLock 中的體現

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState(); // 當前的state
    if (c == 0) { //首次獲取到鎖
        if (compareAndSetState(0, acquires)) { //CAS並設置狀態
            setExclusiveOwnerThread(current); //線程暫存
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) { //判定是否存儲當前線程, 是的話就是重入
        int nextc = c + acquires; //狀態新增
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc); //狀態回寫
        return true;
    }
    return false;
}

線程通訊

wait()notity() notifyAll() 在 Object 中定義的原因?

一個很明顯的原因是JAVA提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。鎖是線程爭用的對象, 與線程不應該過度連接. Object上也有一些頭信息, 為鎖的管理提供方便.

wait()notity() 為什麽必須在 synchronized 中執行?

很簡單, 多線程環境下的鎖才有意義, synchronized 表明有資源爭用, 即鎖的爭奪, 這是鎖存在的前提.

線程退出

主線程退出時deamon子線程何時退出

關於這個問題, 實際上是一個depands-on的答案, 有時候子線程會繼續執行一段時間, 有時候則在主線程退出後立即退出, 一般認為主線程退出後臺線程也會退出, 但並不能認為子線程一定不會執行.

Shutdown Hook 方法的使用

方法簽名: Runtime#addShutdownHook(Thread thread)

這個鉤子方法可以在線程退出時回調完成一些工作, 比如資源回收,還有一些對象的銷毀工作.

如何確保主線程退出前所有線程執行完畢

ThreadGroup

ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
int count = threadGroup.activeCount();
Thread[] threads = new Thread[count];
threadGroup.enumerate(threads,true); //復制到這個數組
for (Thread t : threads) {
    System.out.println(t.getState());
}

線程相關的幾個問題