日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java多线程知识小抄集(三)

發(fā)布時間:2024/4/11 java 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java多线程知识小抄集(三) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰(zhàn)指南》,同時歡迎關(guān)注筆者的微信公眾號:朱小廝的博客。


歡迎跳轉(zhuǎn)到本文的原文鏈接:https://honeypps.com/java/java-multiple-thread-summary-3/

本文主要整理博主遇到的Java多線程的相關(guān)知識點,適合速記,故命名為“小抄集”。本文沒有特別重點,每一項針對一個多線程知識做一個概要性總結(jié),也有一些會帶一點例子,習題方便理解和記憶。

###51. SimpleDateFormat非線程安全
當多個線程共享一個SimpleDateFormat實例的時候,就會出現(xiàn)難以預料的異常。

主要原因是parse()方法使用calendar來生成返回的Date實例,而每次parse之前,都會把calendar里的相關(guān)屬性清除掉。問題是這個calendar是個全局變量,也就是線程共享的。因此就會出現(xiàn)一個線程剛把calendar設置好,另一個線程就把它給清空了,這時第一個線程再parse的話就會有問題了。

解決方案:1. 每次使用時創(chuàng)建一個新的SimpleDateFormat實例;2. 創(chuàng)建一個共享的SimpleDateFormat實例變量,并對這個變量進行同步;3. 使用ThreadLocal為每個線程都創(chuàng)建一個獨享的SimpleDateFormat實例變量。

###52. CopyOnWriteArrayList
在每次修改時,都會創(chuàng)建并重新發(fā)布一個新的容器副本,從而實現(xiàn)可變現(xiàn)。CopyOnWriteArrayList的迭代器保留一個指向底層基礎數(shù)組的引用,這個數(shù)組當前位于迭代器的起始位置,由于它不會被修改,因此在對其進行同步時只需確保數(shù)組內(nèi)容的可見性。因此,多個線程可以同時對這個容器進行迭代,而不會彼此干擾或者與修改容器的線程相互干擾?!皩憰r復制”容器返回的迭代器不會拋出ConcurrentModificationException并且返回的元素與迭代器創(chuàng)建時的元素完全一致,而不必考慮之后修改操作所帶來的影響。顯然,每當修改容器時都會復制底層數(shù)組,這需要一定的開銷,特別是當容器的規(guī)模較大時,僅當?shù)僮鬟h遠多于修改操作時,才應該使用“寫入時賦值”容器。

###53. 工作竊取算法(work-stealing)
工作竊取算法是指某個線程從其他隊列里竊取任務來執(zhí)行。在生產(chǎn)-消費者設計中,所有消費者有一個共享的工作隊列,而在work-stealing設計中,每個消費者都有各自的雙端隊列,如果一個消費者完成了自己雙端隊列中的全部任務,那么它可以從其他消費者雙端隊列末尾秘密地獲取工作。

優(yōu)點:充分利用線程進行并行計算,減少了線程間的競爭。
缺點:在某些情況下還是存在競爭,比如雙端隊列(Deque)里只有一個任務時。并且該算法會消耗了更多的系統(tǒng)資源,比如創(chuàng)建多個線程和多個雙端隊列。

###54. Future & FutureTask
FutureTask表示的計算是通過Callable來實現(xiàn)的,相當于一種可生產(chǎn)結(jié)果的Runnable,并且可以處于一下3種狀態(tài):等待運行,正在運行和運行完成。運行表示計算的所有可能結(jié)束方式,包括正常結(jié)束、由于取消而結(jié)束和由于異常而結(jié)束等。當FutureTask進入完成狀態(tài)后,它會永遠停止在這個狀態(tài)上。Future.get的行為取決于任務的狀態(tài),如果任務已經(jīng)完成,那么get會立刻返回結(jié)果,否則get將阻塞知道任務進入完成狀態(tài),然后返回結(jié)果或者異常。FutureTask的使用方式如下:

public class Preloader {//method1private final static FutureTask<Object> future = new FutureTask<Object>(new Callable<Object>(){@Overridepublic Object call() throws Exception{return "yes";}});//method2static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());private static final Future<Object> futureExecutor = executor.submit(new Callable<Object>(){@Overridepublic Object call() throws Exception{return "no";}}); public static void main(String[] args) throws InterruptedException, ExecutionException{executor.shutdown();future.run();System.out.println(future.get());System.out.println(futureExecutor.get());} }

運行結(jié)果:yes no
Callable表示的任務可以拋出受檢查或未受檢查的異常,并且任何代碼都可能拋出一個Error.無論任務代碼拋出什么異常,都會被封裝到一個ExecutionException中,并在Future.get中被重新拋出。

###55. Executors
newFixedThreadPool:創(chuàng)建一個固定長度的線程池,每當提交一個任務時就創(chuàng)建一個線程,直到達到線程池的最大數(shù)量,這時線程池的規(guī)模將不再變化(如果某個線程由于發(fā)生了未預期的Exception而結(jié)束,那么線程池會補充一個新的線程)。(LinkedBlockingQueue)
newCachedThreadPool:創(chuàng)建一個可換成的線程池,如果線程池的當前規(guī)模超過了處理需求時,那么將回收空閑的線程,而當需求增加時,則可以添加新的線程,線程池的規(guī)模不存在任何限制。(SynchronousQueue)
newSingleThreadExecutor:是一個單線程的Executor,它創(chuàng)建單個工作者線程來執(zhí)行任務,如果這個線程異常結(jié)束,會創(chuàng)建另一個線程來替代。能確保一組任務在隊列中的順序來串行執(zhí)行。(LinkedBlockingQueue)
newScheduledThreadPool:創(chuàng)建了一個固定長度的線程池,而且以延遲或者定時的方式來執(zhí)行任務,類似于Timer。

###56. ScheduledThreadPoolExecutor替代Timer
由第17項可知Timer有兩個缺陷,在JDK5開始就很少使用Timer了,取而代之的可以使用ScheduledThreadPoolExecutor。使用實例如下:

import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit;public class ScheduleThreadPoolTest {private static ScheduledExecutorService exec = Executors.newScheduledThreadPool(2);public static void method1(){exec.schedule(new Runnable(){@Overridepublic void run(){System.out.println("1");}}, 2, TimeUnit.SECONDS);}public static void method2(){ScheduledFuture<String> future = exec.schedule(new Callable<String>(){@Overridepublic String call() throws Exception{return "Callable";}}, 4, TimeUnit.SECONDS);try{System.out.println(future.get());}catch (InterruptedException | ExecutionException e){e.printStackTrace();}}public static void main(String[] args){method1();method2();} }

運行結(jié)果:1 Callable

###57. Callable & Runnable
Executor框架使用Runnable作為基本的任務表示形式。Runnable是一種有很大局限的抽象,雖然run能寫入到日志文件或者將結(jié)果放入某個共享的數(shù)據(jù)結(jié)構(gòu),但它不能返回一個值或拋出一個受檢查的異常。

許多任務實際上都是存在延遲的計算——執(zhí)行數(shù)據(jù)庫查詢,從網(wǎng)絡上獲取資源,或者計算某個復雜的功能。對于這些任務,Callable是一種更好的抽象:它認為主入口點(call())將返回一個值,并可能拋出一個異常。

Runnable和Callable描述的都是抽象的計算任務。這些任務通常是有范圍的,即都有一個明確的起始點,并且最終會結(jié)束。

###58. CompletionService
如果想Executor提交了一組計算任務,并且希望在計算完成后獲得結(jié)果,那么可以保留與每個任務關(guān)聯(lián)的Future,然后反復使用get方法,同事將參數(shù)timeout指定為0,從而通過輪詢來判斷任務是否完成。這種方法雖然可行,但卻有些繁瑣。幸運的是,還有一種更好的方法:CompletionService。CompletionService將Executor和BlockingQueue的功能融合在一起。你可以將Callable任務提交給它來執(zhí)行,然后使用類似于隊列操作的take和poll等方法來獲得已完成的結(jié)果,而這些結(jié)果會在完成時被封裝為Future。ExecutorCompletionService實現(xiàn)了CompletionService,并將計算部分委托到一個Executor。代碼示例如下:

int coreNum = Runtime.getRuntime().availableProcessors();ExecutorService executor = Executors.newFixedThreadPool(coreNum);CompletionService<Object> completionService = new ExecutorCompletionService<Object>(executor);for(int i=0;i<coreNum;i++){completionService.submit( new Callable<Object>(){@Overridepublic Object call() throws Exception{return Thread.currentThread().getName();}});}for(int i=0;i<coreNum;i++){try{Future<Object> future = completionService.take();System.out.println(future.get());}catch (InterruptedException | ExecutionException e){e.printStackTrace();}}

運行結(jié)果:

pool-1-thread-1 pool-1-thread-2 pool-1-thread-3 pool-1-thread-4

可以通過ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue)構(gòu)造函數(shù)指定特定的BlockingQueue(如下代碼剪輯),默認為LinkedBlockingQueue。

BlockingQueue<Future<Object>> bq = new LinkedBlockingQueue<Future<Object>>();CompletionService<Object> completionService = new ExecutorCompletionService<Object>(executor,bq);

ExecutorCompletionService的JDK源碼只有100行左右,有興趣的朋友可以看看。

###59. 通過Future來實現(xiàn)取消
ExecutorService.submit將返回一個Future來描述任務。Future擁有一個cancel方法,該方法帶有一個boolean類型的參數(shù)mayInterruptIfRunning,表示取消操作是否成功。如果mayInterruptIfRunning為true并且任務當前正在某個線程運行,那么這個線程能被中斷。如果這個參數(shù)為false,那么意味著“若任務還沒啟動,就不要運行它”,這種方式應該用于那些不處理中斷的任務中。當Future.get拋出InterruptedException或TimeoutException時,如果你知道不再需要結(jié)果,那么就可以調(diào)用Futuure.cancel來取消任務。

###60. 處理不可中斷的阻塞
對于一下幾種情況,中斷請求只能設置線程的中斷狀態(tài),除此之外沒有其他任何作用。

  • Java.io包中的同步Socket I/O:雖然InputStream和OutputStream中的read和write等方法都不會響應中斷,但通過關(guān)閉底層的套接字,可以使得由于執(zhí)行read或write等方法而被阻塞的線程拋出一個SocketException。
  • Java.io包中的同步I/O:當中斷一個在InterruptibleChannel上等待的線程時會拋出ClosedByInterrptException并關(guān)閉鏈路。當關(guān)閉一個InterruptibleChannel時,將導致所有在鏈路操作上阻塞的線程都拋出AsynchronousCloseException。
  • Selector的異步I/O:如果一個線程在調(diào)用Selector.select方法時阻塞了,那么調(diào)用close或wakeup方法會使線程拋出ClosedSelectorException并提前返回。
  • 獲得某個鎖:如果一個線程由于等待某個內(nèi)置鎖而阻塞,那么將無法響應中斷,因為線程認為它肯定會獲得鎖,所以將不會理會中斷請求,但是在Lock類中提供了lockInterruptibly方法,該方法允許在等待一個鎖的同時仍能響應中斷。

###61. 關(guān)閉鉤子
JVM既可以正常關(guān)閉也可以強制關(guān)閉,或者說非正常關(guān)閉。關(guān)閉鉤子可以在JVM關(guān)閉時執(zhí)行一些特定的操作,譬如可以用于實現(xiàn)服務或應用程序的清理工作。關(guān)閉鉤子可以在一下幾種場景中應用:1. 程序正常退出(這里指一個JVM實例);2.使用System.exit();3.終端使用Ctrl+C觸發(fā)的中斷;4. 系統(tǒng)關(guān)閉;5. OutOfMemory宕機;6.使用Kill pid命令干掉進程(注:在使用kill -9 pid時,是不會被調(diào)用的)。使用方法(Runtime.getRuntime().addShutdownHook(Thread hook))。更多內(nèi)容可以參考JAVA虛擬機關(guān)閉鉤子(Shutdown Hook)

###62. 終結(jié)器finalize
終結(jié)器finalize:在回收器釋放它們后,調(diào)用它們的finalize方法,從而保證一些持久化的資源被釋放。在大多數(shù)情況下,通過使用finally代碼塊和顯示的close方法,能夠比使用終結(jié)器更好地管理資源。唯一例外情況在于:當需要管理對象,并且該對象持有的資源是通過本地方法獲得的。但是基于一些原因(譬如對象復活),我們要盡量避免編寫或者使用包含終結(jié)器的類。

###63. 線程工廠ThreadFactory
每當線程池(ThreadPoolExecutor)需要創(chuàng)建一個線程時,都是通過線程功夫方法來完成的。默認的線程工廠方法將創(chuàng)建一個新的、非守護的線程,并且不包含特殊的配置信息。通過指定一個線程工廠方法,可以定制線程池的配置信息。在ThreadFactory中只定義了一個方法newThread,每當線程池需要創(chuàng)建一個新線程時都會調(diào)用這個方法。默認的線程工廠(DefaultThreadFactory 是Executors的內(nèi)部類)如下:

static class DefaultThreadFactory implements ThreadFactory {private static final AtomicInteger poolNumber = new AtomicInteger(1);private final ThreadGroup group;private final AtomicInteger threadNumber = new AtomicInteger(1);private final String namePrefix;DefaultThreadFactory() {SecurityManager s = System.getSecurityManager();group = (s != null) ? s.getThreadGroup() :Thread.currentThread().getThreadGroup();namePrefix = "pool-" +poolNumber.getAndIncrement() +"-thread-";}public Thread newThread(Runnable r) {Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0);if (t.isDaemon())t.setDaemon(false);if (t.getPriority() != Thread.NORM_PRIORITY)t.setPriority(Thread.NORM_PRIORITY);return t;}}

通過implements ThreadFactory可以定制線程工廠。譬如,你希望為線程池中的線程指定一個UncaughtExceptionHandler,或者實例化一個定制的Thread類用于執(zhí)行調(diào)試信息的記錄。

###64. synchronized與ReentrantLock之間進行選擇
由第21條可知ReentrantLock與synchronized想必提供了許多功能:定時的鎖等待,可中斷的鎖等待、公平鎖、非阻塞的獲取鎖等,而且從性能上來說ReentrantLock比synchronized略有勝出(JDK6起),在JDK5中是遠遠勝出,為嘛不放棄synchronized呢?ReentrantLock的危險性要比同步機制高,如果忘記在finnally塊中調(diào)用unlock,那么雖然代碼表面上能正常運行,但實際上已經(jīng)埋下了一顆定時炸彈,并很可能傷及其他代碼。僅當內(nèi)置鎖不能滿足需求時,才可以考慮使用ReentrantLock.

###65. Happens-Before規(guī)則
程序順序規(guī)則:如果程序中操作A在操作B之前,那么在線程中A操作將在B操作之前。
監(jiān)視器鎖規(guī)則:一個unlock操作現(xiàn)行發(fā)生于后面對同一個鎖的lock操作。
volatile變量規(guī)則:對一個volatile變量的寫操作先行發(fā)生于后面對這個變量的讀操作,這里的“后面”同樣是指時間上的先后順序。
線程啟動規(guī)則:Thread對象的start()方法先行發(fā)生于此線程的每一個動作。
線程終止規(guī)則:線程的所有操作都先行發(fā)生于對此線程的終止檢測,我們可以通過Thread.join()方法結(jié)束、Thread.isAlive()的返回值等于段檢測到線程已經(jīng)終止執(zhí)行。
線程中斷規(guī)則:線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生。
終結(jié)器規(guī)則:對象的構(gòu)造函數(shù)必須在啟動該對象的終結(jié)器之前執(zhí)行完成。
傳遞性:如果操作A先行發(fā)生于操作B,操作B先行發(fā)生于操作C,那就可以得出操作A先行發(fā)生于操作C的結(jié)論。

注意:如果兩個操作之間存在happens-before關(guān)系,并不意味著java平臺的具體實現(xiàn)必須要按照Happens-Before關(guān)系指定的順序來執(zhí)行。如果重排序之后的執(zhí)行結(jié)果,與按happens-before關(guān)系來執(zhí)行的結(jié)果一致,那么這種重排序并不非法。

###66. as-if-serial
不管怎么重排序,程序執(zhí)行結(jié)果不能被改變。

###67. ABA問題
ABA問題發(fā)生在類似這樣的場景:線程1轉(zhuǎn)變使用CAS將變量A的值替換為C,在此時,線程2將變量的值由A替換為C,又由C替換為A,然后線程1執(zhí)行CAS時發(fā)現(xiàn)變量的值仍為A,所以CAS成功。但實際上這時的現(xiàn)場已經(jīng)和最初的不同了。大多數(shù)情況下ABA問題不會產(chǎn)生什么影響。如果有特殊情況下由于ABA問題導致,可用采用AtomicStampedReference來解決,原理:樂觀鎖+version??梢詤⒖枷旅娴陌咐齺砹私馄渲械牟煌?/p> public class ABAQuestion {private static AtomicInteger atomicInt = new AtomicInteger(100);private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100,0);public static void main(String[] args) throws InterruptedException{Thread thread1 = new Thread(new Runnable(){@Overridepublic void run(){atomicInt.compareAndSet(100, 101);atomicInt.compareAndSet(101, 100);}});Thread thread2 = new Thread(new Runnable(){@Overridepublic void run(){try{TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e){e.printStackTrace();}boolean c3 = atomicInt.compareAndSet(100, 101);System.out.println(c3);}});thread1.start();thread2.start();thread1.join();thread2.join();Thread thread3 = new Thread(new Runnable(){@Overridepublic void run(){try{TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e){e.printStackTrace();}atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);}});Thread thread4 = new Thread(new Runnable(){@Overridepublic void run(){int stamp = atomicStampedRef.getStamp();try{TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e){e.printStackTrace();}boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp+1);System.out.println(c3);}});thread3.start();thread4.start();} }

輸出結(jié)果:true false


持續(xù)更新中~
博主嘔心瀝血整理發(fā)布,跪求一贊。

wanna more?
Java多線程知識小抄集(一)
Java多線程知識小抄集(二)


參考資料

  • 《Java多線程編程核心技術(shù)》高洪巖著。
  • 《Java并發(fā)編程的藝術(shù)》方騰飛 等著。
  • 《Java并發(fā)編程實戰(zhàn)》Brian Goetz等著。
  • 深入JDK源碼之ThreadLocal類
  • JAVA多線程之擴展ThreadPoolExecutor
  • Comparable與Comparator淺析
  • JAVA多線程之UncaughtExceptionHandler——處理非正常的線程中止
  • JAVA線程間協(xié)作:Condition
  • JAVA線程間協(xié)作:wait.notify.notifyAll
  • Java中的鎖
  • Java守護線程概述
  • Java SimpleDateFormat的線程安全性問題
  • JAVA虛擬機關(guān)閉鉤子(Shutdown Hook)
  • 歡迎跳轉(zhuǎn)到本文的原文鏈接:https://honeypps.com/java/java-multiple-thread-summary-3/

    歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰(zhàn)指南》,同時歡迎關(guān)注筆者的微信公眾號:朱小廝的博客。


    超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術(shù)人生

    總結(jié)

    以上是生活随笔為你收集整理的Java多线程知识小抄集(三)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。