《瘋狂的JAVA講義》筆記-第16章多線程

《瘋狂的JAVA講義》筆記-第16章多線程

  • 三種方法實現多線程
實現方法 特點 例子
繼承 Thread 無法共享變量,以run爲線程執行體 new Thread().start
實現 Runnable接口,並作爲 Thread的target 運行 共享實現類變量,以run爲線程執行體 new Thread(new MyRun()).start
實現Callable接口,並使用FutureTask包裝,把包裝對象作爲Thread的target運行 可以拋異常且有返回值(task.get()),共享變量,以call爲線程執行體 task = new FutureTask(new MyCall()); Thread thread = new Thread(task);
  • sleepyield 以及join

    • sleep:讓線程睡眠一段時間,處於 阻塞狀態。
    • join:讓線程等待另一個線程執行結束,可用於流程性的控制
    • yield: 線程讓步,不阻塞線程,只是將線程設爲 就緒狀態,將CPU讓給優先級更高的線程。可能會出現沒有其他優先級更高的線程,從而剛成爲就緒狀態後又被操作系統調度成爲 運行 狀態的線程
  • 後臺線程/守護線程

    • 爲其他線程服務的,一直在後臺運行的線程,可以通過thread.setDaemon(true)設置,但是須在運行前進行設置,且中途不能修改。當所有的前臺程序結束,後臺線程自動結束。例子:JVM的GC
  • 線程同步

    • 同步代碼塊:同步監視器(互斥量)爲 obj,只有獲取到該監視器纔可以進入代碼塊
    synchronized(obj){
    ....
    }
    
    • 同步方法:同步監視器爲this,只有獲取到account實例纔可以進入代碼塊
    //Account.java
    public synchronized int method(){
    ....
    }
    
    • 靈活的加鎖同步Lock :較爲常用的是可重入鎖ReetrantLocksynchronized 也是可重入鎖
    private final lock = new ReentrantLock();
    public int method(){
    	lock.lock();
    	try{
    		...
    	}finally{
    		lock.unlock();
    	}
    }
    
  • 可重入鎖與不可重入鎖

    • 可重入鎖:當一個進程獲取到 A 鎖,如果在遞歸或者其他需要 A 鎖的地方時,無需再次獲取 A 鎖。
    • 不可重入鎖:需要再次獲取 A 鎖,容易導致死鎖
    	public synchronized int method1(){
    		method2();
    	}
    	public synchronized int method2(){
    			.....
    	}
    //這種情況下 可重入不會死鎖,但 不可重入則會死鎖
    
  • 鎖的釋放

    • 釋放鎖的情景
    1. 方法執行結束
    2. 同步代碼塊或同步方法執行了 break 或 return 語句
    3. 出現異常,方法結束
    4. 程序執行了 wait()
    • 不會釋放鎖的情景
    1. 調用sleep()yield() 方法
    2. 其他線程調用其他線程的 suspend()方法
  • 進程通信:當使用 同步方法時,同步監視器是 this,即對象本身,可以使用 Object類的wait``notify``notifyAll方法進行通信

    • wait():讓當前進程暫停,直到其他線程調用 該同步監視器 的 notifynotifyAll方法才被喚醒,也可以 等指定秒數後自行喚醒
    • notify():喚醒 在同一個同步監視器上等待的單個線程。如果有多個,則選擇是任意性的
    • notifyAll():喚醒所有在同一個同步監視器上等待的線程

    如果不使用 同步方法,則不存在 wait()等方法,可以使用 Condition 來進行類似的操作

    • await() :跟wait()相似,讓當前線程暫停,並釋放已經獲得的 Lock 對象
    • signal()/signalAll():與 notify()/notifyAll()一樣
  • 使用 阻塞隊列 控制通信:消費者/生產者模型

    • 使用 take() put()可以使進程阻塞
  • 線程池

    • 由於頻繁創建銷燬非常影響性能,所以在 JAVA 5 後有了線程池,可使用工廠類 Executors的以下方法產生線程池
    • newCachedThreadPool():根據需要創建線程,並將線程緩存在線程池裏
    • newFixedThreadPool():創建固定長度,可複用的線程池
    • newSingleThreadPool(): 創建只有一個線程的線程池
    • newScheduledThreadPool(int corePoolSize) :創建指定線程線程數的線程池,可以在指定延遲後執行線程任務

    以上方法返回一個 ExecutorService 對象,有以下方法

    • Future<?> submit(Runnable task) :由於 run方法沒有返回值,所以返回值爲 null,可以調用Future對象的 isDone/isCancelled方法獲取執行狀態
    • <T> Future<T> submit(Runnable task,T result):線程池在空閒時會執行 task 任務,在結束後返回 result
    • <T> Future<T> submit(Callable<T> task)Future對象是call()方法的返回值

    當不想提交任務後,請使用shutdown()方法關閉線程池,但是線程池中線程是在執行完當前任務後纔會死亡,如果需要立即死亡,使用shutdownNow()方法,可以視圖停止所有正在執行的任務

  • ForkJoinPool

    • 單核上多線程只能算 併發,但是多核上就可以實現 並行(parallelism),ForkJoinPool線程就是實現這個的,他是 ExecutorService的實現類
    • ForkJoinPool():根據 Runtime.availableProcessors()方法指定 並行線程數,當然也可以傳入 int parallelism 指定並行線程數
    • 沒有返回值的任務需要實現 RecursiveAction,有返回值的任務則實現 RecursiveTask<T> ,最後使用 submit方法提交即可
  • TreadLocal類可以實現每個線程都使用自己的變量副本,即每個線程的該變量都是獨立的,不會發生死鎖、等待鎖的情況。使用方法 private ThreadLocal<String> name = new ThreadLocal<>();

END

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