天天看點

Java Concurrency In Practice

定義

a class is thread-safe if it behaves correctly when accessed from multiple threads, regardless of the scheduling or interleaving of the execution of those threads by the runtime environment, and with no additional synchronization or other coordination on the part of the calling code.

a stateless servlet

獨立的, 無狀态的, 這種程式一定是線程安全的

競争條件(race conditions), 原子性問題

做了如下改動, 這個代碼就是非線程安全的, 因為他存在race conditions

++count, 是典型的read-modify-write operation, 非原子的, 在多線程情況下, 你無法保證在read到write的過程中, count不被改動, 所有運作結果取決于執行順序, 這就産生競争條件

多個程序并發通路和操作同一資料且執行結果與通路的特定順序有關, 稱為競争條件(race conditions)

可見性問題

直接看例子, 下面的代碼會得到什麼結果, 正常的期望是子線程結束, 并列印出42 

但是結果會出人意外, 每次都不一定, 有可能子線程根本就不終止, 或列印出0 

這就是典型的可見性問題, 由于缺乏同步機制, 你在主線程中修改number和ready的值, 在子線程中不一定是可見的

對應列印0, 可能會更加令人意外, 看上去ready=true被先執行了, 實際就是這樣的 

這是"reordering”現象, 在單線程中, 如果reordering不會影響最終結果, 那麼編譯器就有可能會做優化調整執行順序

nonatomic 64-bit operations 對于大部分情況下, 即使不加鎖, 還是可以滿足"out-of-thin-air safety”(最底限的安全性), 即雖然有可能讀到過期資料, 但畢竟是真實存在的資料  但對于64-bit numeric variables (double and long) that are not declared volatile, 卻是例外, 會産生更大的問題 因為java會将一個64位的操作分成2個32位的操作, 這就存在在不同線程中和讀到寫到一半的long或double

顯而易見, 最常用的方法就是用鎖來解決線程安全問題, 雖然鎖是一種極高耗費的操作 

在java中鎖可以同時解決原子性問題和可見性問題 

通過鎖來解決原子性問題, 比較顯而易見, 而解決可見性問題就稍微陰晦一些,是以通過下面的圖說明 

Java Concurrency In Practice

java對鎖做了封裝, 可以友善的使用synchronized來解決, 當然你也可以顯式的使用lock(不推薦,除非特殊情況)

a synchronized block has two parts: a reference to an object that will serve as the lock, and a block of code to be guarded by that lock.

every java object can implicitly act as a lock for purposes of synchronization; these built-in locks are called intrinsic locksor monitor locks. 

the lock is automatically acquired by the executing thread before entering a synchronized block and automatically released when control exits the synchronized block, whether by the normal control path or by throwing an exception out of the block. the only way to acquire an intrinsic lock is to enter a synchronized block or method guarded by that lock.

需要注意的是, 這種鎖雖然很友善, 但是他鎖的是那段邏輯, 和你需要保護的那個屬性是沒有關系的, 其他線程可以通過其他的邏輯任意修改這個屬性 

是以是否達成線程安全, 是需要開發者設計和保證的

下面給出如何利用synchronized來解決線程安全問題 

需要平衡安全和效率, 是以實作沒有對整個service函數加鎖, 而是隻是對其中部分需要互斥邏輯加鎖

reentrancy, 可重入性

reentrancy means that locks are acquired on a per-thread rather than per-invocation basis. 意味着同一個線程可以多次lock這個鎖, 隻是增加鎖計數 

看下面的例子, 如果不支援reentrancy, 就會導緻死鎖

鎖是一個最平庸的方案, 但問題是比較低效, 是以java也支援一些更高效的方法來解決線程安全問題

volatile, 解決可見性問題 

底層應該是通過memory barrier來實作的 

被聲明為volatile的變量, 

被寫入後, 會将change立即從cache更新到memory中, 等同于離開synchronized區域(從可見性上看) 

被讀時, 如果已經被其他線程改變, 也會先從memory中同步最新資料, 等同于進入synchronized區域(從可見性上看)

但需要注意的是, volatile隻解決可見性問題, 而不能解決原子性問題, 這個還需要其cas配合, 稱為lock-free技術

另外, memory barrier還能保證有序性, 在memory barrier之前寫的指令一定先于之後寫的指令

解決并發問題最簡單, 最安全的方法, 就是直接使用線程安全的容器和類來管理狀态

同步容器

在談并發容器前, 先看看早期的java版本中提供同步容器, vector and hashtable, 他們是線程安全的 

問題是, 對整個資料結構都隻能串行操作, 這個效率是非常低下的, 尤其當size比較大的時候

并且也無法應對複合操作, 比如put-if-absent, read-modify-write, 仍然要在用戶端加鎖, 效率更低, 因為加了兩遍鎖

比如下面的deletelast, 很有可能會抛arrayindexoutofboundsexception異常

是以仍然需要加上鎖, 很低效

對于比較新的collection類而言, 這些問題仍然存在, 因為它本身就不是線程安全的, 尤其對于大量使用的iterator 

因為它是fail-fast, 即在疊代過程中發現容器被修改, 會抛出unchecked concurrentmodificationexception 

而且在collection的操作中處處都隐含了對iterator的使用, 比如for each, tostring, hashcode and equals

是以下面就介紹一些并發容器, 以友善并發處理

concurrenthashmap

concurrenthashmap is a concurrent replacement for a synchronized hash-based map, java 6 adds concurrentskiplistmap and concurrentskiplistset, which are concurrent replacements for a synchronized sortedmap or sortedset (such as treemap or treeset wrapped with synchronizedmap).

concurrenthashmap用于替換現有的同步hashmap, 并且在java 6裡面增加了concurrentskiplistmap, concurrentskiplistset替換現有的同步的treemap,treeset, 從名字上看, 應該是用skiplist來替換紅黑樹

并且在讀的時候不用加鎖, 原因是對get要通路的共享變量都使用了voliate, 保證讀到最新資料 

concurrenthashmap還提高了複合操作的接口, 把他們原子化, 你可以直接使用

copyonwritearraylist

copyonwritearraylist is a concurrent replacement for a synchronized list that offers better concurrency in some common situations and eliminates the need to lock or copy the collection during iteration. (similarly, copyonwritearrayset is a concurrent replacement for a synchronized set.)

copyonwrite技術, 用于很多地方, 隻有讀操作的時候保留一份copy, 隻有當寫操作發生的時候才建立新的copy, 用于change, 這樣舊的copy仍然可以被并發通路 

當然copy是需要開銷的, 是以比較适合大量讀操作的場景 

讀的時候沒有問題, 不過要注意在用iterator時候, 它其實是做了snapshot, 新的改動是讀不到的, 并且疊代過程中不能change, 否則會報異常 

寫的時候需要加鎖, 是同步的, 先加鎖,拷貝一份新數組對象,在新的對象上改動,指派回去,解鎖

blockingqueue

傳統的queue和priorityqueue都需要使用者去自己控制并發同步 

blockingqueue很容易的就可以實作, produer/consumer模式

Java Concurrency In Practice

arrayblockingqueue: 基于數組的阻塞隊列實作, input和output指針公用一把鎖, 無法實作真正的并發, 但在插入和删除資料是不需要産生和銷毀node對象

linkedblockingqueue: 基于連結清單的阻塞隊列, 最常用到的blockingqueue, input和output指針使用獨立的鎖, 是以較好的并發性. 需要注意的是, 預設容量是integer.max_value, 是以最好指定大小, 否則會爆記憶體

delayqueue: 當元素被放入到queue中後, 必須過了延遲時間後, 才能被取出. 應用場景, 管理一個逾時未響應的連接配接隊列

priorityblockingqueue: 基于優先級的阻塞隊列(優先級的判斷通過構造函數傳入的compator對象來決定), 但需要注意的是priorityblockingqueue并不會阻塞資料生産者, 是以當心爆記憶體

synchronousqueue: 無緩沖的等待隊列, producer直接把資料給consumer, 而不用真正使用作為中介的queue

blockingdeque

Java Concurrency In Practice

關鍵是deque可以實作work stealing模式 

典型場景, 多個隊列, 多個consumer, 為了避免競争的耗費, 可以每個queue配置設定一個consumer, 但這樣會有配置設定不均的情況, 是以可以讓空閑的consumer從隊尾取資料, 以避免在隊頭産生競争

concurrentlinkedqueue

這個應該是最高效的并發queue, 因為不是使用悲觀鎖的blocking, 号稱是"lock free” 

其實是使用volatile和cas等更底層的機制, 來實作并發互斥

<a href="http://www.infoq.com/cn/articles/concurrentlinkedqueue?utm_source=infoq&amp;utm_medium=related_content_link&amp;utm_campaign=relatedcontent_articles_clk">聊聊并發(六)——concurrentlinkedqueue的實作原理分析</a>

用于并發同步的對象都可以稱為synchronizer, 是以前面提到的并發容器也都是synchronizer 

還有其他一些synchronizer

countdownlatch

countdownlatch是一種閉鎖, 它通過内部一個計數器count來标示狀态, 當count&gt;0時, 所有調用其await方法的線程都需等待, 當通過其countdown方法将count降為0時所有等待的線程将會被喚起執行 

應用場景, 倒計時, 當count減為0時, 所有線程一起執行

semaphore

類實際上就是作業系統中談到的信号量的一種實作, 提供acquire和release方法來實作資源管理

cyclicbarrier

和latch有點象, 隻是解鎖條件不同 

barrier用于一組線程互相等待,直到到達某個公共屏障點 (common barrier point)時, 一起被執行, 因為該barrier在釋放等待線程後可以重用,是以稱它為循環的barrier 

場景, 旅遊時集合, 必須等所有人都到齊, 才能去下個景點

future接口

a future represents the result of an asynchronous computation.

cancel(boolean mayinterruptifrunning): 取消任務的執行, 參數指定是否立即中斷任務執行,或者等等任務結束

iscancelled: 任務是否已經取消, 任務正常完成前将其取消, 則傳回 true

isdone: 任務是否已經完成, 需要注意的是如果任務正常終止、異常或取消,都将傳回true

v get(): 等待任務執行結束, 然後獲得v類型的結果 

異常類型, interruptedexception 線程被中斷異常, executionexception任務執行異常, <code>cancellationexception</code>任務被取消

v get (long timeout, timeunit unit) timeout 逾時時間,uint 時間機關 

除了上面的異常, 還會抛出逾時異常timeoutexception

futuretask

the futuretask class is an implementation of future that implements runnable, and so may be executed by an executor.

callable和runnable  實作callable接口的類和實作runnable的類都是可被其它線程執行  - callable和runnable有幾點不同:    - callable規定的方法是call(),而runnable規定的方法是run().  - callable的任務執行後可傳回值,而runnable的任務是不能傳回值的。    - call()方法可抛出異常,而run()方法是不能抛出異常的。  - 運作callable任務可拿到一個future對象

futuretask是對future接口的實作, 并且還實作了runable是以可以被executor直接執行

futuretask利用callable來實作, 把異步邏輯封裝成callable, callable會傳回future對象 

可以通過future接口來操作這個future對象 

并且futuretask還提供done接口來提供callback, 當isdone為true被自動調用, 預設是空邏輯, 需要override自己的邏輯

作為上面内容的很好的總結例子, 實作對高耗費計算的結果的buffer, 并提高并發支援, 直接看代碼

直接使用線程, 你需要自己管理線程的生命周期, 比較麻煩 

java se5的java.util.concurrent包中的executor将為你管理thread對象, 進而簡化了并發程式設計 

包括threadpool,executor,executors,executorservice,completionservice,future,callable等類

executor interface

executor接口本身很簡單, 表明隻能執行runnable對象

簡單的接口, 卻是強大framework的基礎, 和producer/consumer模式結合, 就可以簡單的分離方法的調用和執行 

producer傳入runable, consumer就是executor負責execute

executorservice interface

擴充了executor接口, 提供了更多的生命周期管理的接口 

executorservice interface extends executor, adding a number of methods for lifecycle management.

executors class 

executors是工廠和工具類, 最主要是可以用于建立threadpool 

you can create a thread pool by calling one of the static factory methods in executors:

newfixedthreadpool. 

a fixed-size thread pool creates threads as tasks are submitted, up to the maximum pool size, and then attempts to keep the pool size constant (adding new threads if a thread dies due to an unexpected exception).

newcachedthreadpool. 

a cached thread pool has more flexibility to reap idle threads when the current size of the pool exceeds the demand for processing, and to add new threads when demand increases, but places no bounds on the size of the pool.

newsinglethreadexecutor. 

a single-threaded executor creates a single worker thread to process tasks, replacing it if it dies unexpectedly. tasks are guaranteed to be processed sequentially according to the order imposed by the task queue (fifo, lifo, priority order).

newscheduledthreadpool. 

a fixed-size thread pool that supports delayed and periodic task execution, similar to timer.

使用executor來構造web server

completionservice interface

其實這個概念很象select, 送出多個future給executor, future完成的先後不一定, 需要輪詢check 

completionservice提供一種封裝, 對一組送出給completionservice的future對象, 隻會把已經完成的future對象放到内部的blockingqueue裡面, 是以你就不用輪詢, 直接用take取出來的一定是完成的

class executorcompletionservice 接口的實作

nonblocking algorithms, which use low-level atomic machine instructions such as compare-and-swap instead of locks to ensure data integrity under concurrent access.

nonblocking algorithms are considerably more complicated to design and implement than lock-based alternatives, but they can offer significant scalability and liveness advantages.

基于底層的樂觀鎖和volatile的機制

在激烈競争的情況下, 鎖會優于cas, 但是在正常競争的情況下, cas會優于鎖

java中如果使用cas, (atomicxxx in java.util.concurrent. atomic)

in java 5.0, low-level support was added to expose cas operations on int, long, and object references, and the jvm compiles these into the most efficient means provided by the underlying hardware.

there are twelve atomic variable classes, divided into four groups: scalars, field updaters, arrays, and compound variables. the most commonly used atomic variables are the scalars: atomicinteger, atomiclong, atomicboolean, and atomicreference.

主要包括三類: 

1、類 annotation(注解) 

就像名字一樣,這些注解是針對類的。主有要以下三個: 

@immutable 

@threadsafe 

@notthreadsafe

@threadsafe 是表示這個類是線程安全的。具體是否真安全,那要看實作者怎麼實作的了,反正打上這個标簽隻是表示一下。不線程安全的類打上這個注解也沒事兒。 

@immutable 表示,類是不可變的,包含了@threadsafe的意思。 

      @notthreadsafe 表示這個類不是線程安全的。如果是線程安全的非要打上這個注解,那也不會報錯。

這三個注解,對使用者和維護者是有益的,使用者可以立即看出來這個類是否是線程安全的,維護者則是可以根據這個注解,重點檢查線程安全方面。另外,代碼分析工具可能會利用這個注解。

2、域 annotation(注解) 

域注解是對類裡面成員變量加的注解。 

3、方法 annotation(注解) 

方法注解是對類裡面方法加的注解。

域注解和方法注解都是用@guardedby( lock )來辨別。裡面的lock是告訴維護者:這個狀态變量,這個方法被哪個鎖保護着。這樣可以強烈的提示類的維護者注意這裡。

@guardedby( lock )有以下幾種使用形式:

1、@guardedby( "this" ) 受對象内部鎖保護 

2、@guardedby( "fieldname" ) 受 與fieldname引用相關聯的鎖 保護。 

3、@guardedby( "classname.fieldname" ) 受 一個類的靜态field的鎖 儲存。 

4、@guardedby( "methodname()" ) 鎖對象是 methodname() 方法的返值,受這個鎖保護。 

5、@guardedby( "classname.class" ) 受 classname類的直接鎖對象保護。而不是這個類的某個執行個體的鎖對象。

本文章摘自部落格園,原文釋出日期:2013-11-18