Java 併發編程
併發和並行
-
什麼是併發
併發是指兩個或多個事件在同一時間間隔發生,但是實際上處理的只能是其中的一個,但是可以交替去處理其他的。 -
什麼是並行
兩個或多個事件在同一時刻發生被處理。 -
併發和並行區別
一個是交替執行,一個是同時執行.
什麼是線程,與進程的區別
@TODO
線程池
submit() 和 execute()
區別
- 參數不同
submit和execute由於參數不同有四種實現形式
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
void execute(Runnable command);
- submit有返回值,execute沒有
submit在執行過程中不會拋出異常,而是把異常保存在成員變量中,在Future.get()阻塞獲取到異常信息。
execute直接拋出異常之後線程就結束了,submit保存異常線程沒有結束。
爲什麼不允許使用 Executors 創建線程池
線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式。
- Executors 返回的線程池對象的弊端
- FixedThreadPool 和 SingleThreadPool:允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
- CachedThreadPool 和 ScheduledThreadPool:允許的創建線程數量爲 Integer.MAX_VALUE, 可能會創建大量的線程,從而導致 OOM。
Positive example 1:
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
Positive example 2:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
Positive example 3:
<bean id="userThreadPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="2000" />
<property name="threadFactory" value= threadFactory />
<property name="rejectedExecutionHandler">
<ref local="rejectedExecutionHandler" />
</property>
</bean>
//in code
userThreadPool.execute(thread);
線程安全
線程安全和內存模型的關係
- 內存模型
Java內存模型(JMM)包含:工作內存(本地內存)和主內存
工作內存:工作內存可以簡單理解爲計算機當中的CPU高速緩存,但又不完全等同。每一個線程擁有自己的工作內存,對於一個共享變量來說,工作內存當中存儲了它的“副本”。
主內存:主內存可以簡單理解爲計算機當中的內存,但又不完全等同。主內存被所有的線程所共享,對於一個共享變量(比如靜態變量,或是堆內存中的實例)來說,主內存當中存儲了它的“本尊”。
- volatile
- volatile關鍵字具有許多特性,其中最重要的特性就是保證了用volatile修飾的變量對所有線程的可見性。當一個線程修改了變量的值,新的值會立刻同步到主內存當中。而其他線程讀取這個變量的時候,也會從主內存中拉取最新的變量值。爲什麼volatile關鍵字可以有這樣的特性?這得益於java語言的先行發生原則(happens-before)。
- 阻止編譯時和運行時的指令重排。編譯時JVM編譯器遵循內存屏障的約束,運行時依靠CPU屏障指令來阻止重排。
鎖
鎖的類型
悲觀鎖和樂觀鎖
悲觀鎖
悲觀鎖是就是悲觀思想,即認爲寫多,遇到併發寫的可能性高,每次去拿數據的時候都認爲別人會修改,所以每次在讀寫數據的時候都會上鎖,這樣別人想讀寫這個數據就會block直到拿到鎖。Java中的悲觀鎖就是Synchronized, AQS框架下的鎖則是先嚐試cas樂觀鎖去獲取鎖,獲取不到,纔會轉換爲悲觀鎖,如RetreenLock。
樂觀鎖
樂觀鎖是一種樂觀思想,即認爲讀多寫少,遇到併發寫的可能性低,每次去拿數據的時候都認爲別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,採取在寫時先讀出當前版本號,然後加鎖操作(比較跟上一次的版本號,如果一樣則更新),如果失敗則要重複讀-比較-寫的操作。
Java中的樂觀鎖基本都是通過CAS操作實現的,CAS(Compare And
Swap)是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗。
關於鎖的詳細介紹:Java中的偏向鎖、輕量級鎖與重量級鎖(synchronized)
死鎖
synchronized
volatile
sleep 和 wait
wait 和 notify
notify 和 notifyAll
ThreadLocal
用於線程間的數據隔離,爲每一個使用該變量的線程提供一個副本,每個線程都可以獨立的改變自己的副本,而不會和其他的副本衝突。
ThreadLocal類中維護一個Map,用於存儲每一個線程的變量副本,Map中元素的鍵爲線程對象,而值爲對應線程的變量副本。
ThreadLocal在Spring中發揮着巨大的作用,在管理Request作用域中的Bean、事務管理、任務調度、AOP等模塊都出現了它的身影。
Spring中絕大部分Bean都可以聲明成Singleton作用域,採用ThreadLocal進行封裝,因此有狀態的Bean就能夠以singleton的方式在多線程中正常工作了。
ThreadLocal使用場合主要解決多線程中數據數據因併發產生不一致問題。ThreadLocal爲每個線程的中併發訪問的數據提供一個副本,通過訪問副本來運行業務,這樣的結果是耗費了內存,單大大減少了線程同步所帶來性能消耗,也減少了線程併發控制的複雜度。
ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡單得多。
ThreadLocal和Synchonized都用於解決多線程併發訪問。但是ThreadLocal與synchronized有本質的區別。synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal爲每一個線程都提供了變量的副本,使得每個線程在某一時間訪問到的並不是同一個對象,這樣就隔離了多個線程對數據的數據共享。而Synchronized卻正好相反,它用於在多個線程間通信時能夠獲得數據共享。
Synchronized用於線程間的數據共享,而ThreadLocal則用於線程間的數據隔離。
當然ThreadLocal並不能替代synchronized,它們處理不同的問題域。Synchronized用於實現同步機制,比ThreadLocal更加複雜。