上一篇文章請參考貓頭鷹的深夜翻譯:核心JAVA併發(一)java
發佈一個對象是指該對象的引用對當前的域以外也可見(好比,從getter方法中獲取一個引用)。要確保一個對象被安全的發佈(即在初始化完成以後發佈),可能須要使用同步。能夠經過如下方法實現安全的發佈:面試
class StaticInitializer { // Publishing an immutable object without additional initialization public static final Year year = Year.of(2017); public static final Set<String> keywords; // Using static initializer to construct a complex object static { // Creating mutable set Set<String> keywordsSet = new HashSet<>(); // Initializing state keywordsSet.add("java"); keywordsSet.add("concurrency"); // Making set unmodifiable keywords = Collections.unmodifiableSet(keywordsSet); } }
class Volatile { private volatile String state; void setState(String state) { this.state = state; } String getState() { return state; } }
class Atomics { private final AtomicInteger state = new AtomicInteger(); void initializeState(int state) { this.state.compareAndSet(0, state); } int getState() { return state.get(); } }
class Final { private final String state; Final(String state) { this.state = state; } String getState() { return state; } }
確保
this
引用不會再初始化過程當中泄漏
class ThisEscapes { private final String name; ThisEscapes(String name) { Cache.putIntoCache(this); this.name = name; } String getName() { return name; } } class Cache { private static final Map<String, ThisEscapes> CACHE = new ConcurrentHashMap<>(); static void putIntoCache(ThisEscapes thisEscapes) { // 'this' reference escaped before the object is fully constructed. CACHE.putIfAbsent(thisEscapes.getName(), thisEscapes); } }
class Synchronization { private String state; synchronized String getState() { if (state == null) state = "Initial"; return state; } }
不變對象的一個很是棒的屬性時,他們是現成安全的,全部無需在其上進行同步。是一個對象成爲不變對象的要求爲:segmentfault
final
類型this
引用在初始化期間不會泄露不變對象的例子:安全
// Marked as final - subclassing is forbidden public final class Artist { // Immutable object, field is final private final String name; // Collection of immutable objects, field is final private final List<Track> tracks; public Artist(String name, List<Track> tracks) { this.name = name; // Defensive copy List<Track> copy = new ArrayList<>(tracks); // Making mutable collection unmodifiable this.tracks = Collections.unmodifiableList(copy); // 'this' is not passed to anywhere during construction } // Getters, equals, hashCode, toString } // Marked as final - subclassing is forbidden public final class Track { // Immutable object, field is final private final String title; public Track(String title) { this.title = title; } // Getters, equals, hashCode, toString }
java.lang.Thread
類用來表示一個應用或是一個JVM現場。其代碼一般在某個進程類的上下文中執行。(使用Thread#currentThread
來獲取當前線程自己)微信
線程的狀態和相應的描述:併發
NEW: 還未啓動
RUNNABLE: 啓動並運行
BLOCKED: 在控制器上等待 - 該線程正視圖獲取鎖並進入關鍵區域
WAITING: 等待另外一個線程執行特殊操做(notify/notifyAll
,LockSupport#unpark
)
TIMED_WAITING: 和WAITING相似,可是有超時設置
TERMINATED: 中止
Thread的方法和相應的描述:異步
start: 啓動一個Thread
實例而且執行run()
方法
join: 阻塞直到線程完成
interrupt: 中斷線程。若是該線程在響應終端的方法中阻塞着,則會在另外一個線程中拋出InterruptedException
,不然將會被設置爲中斷狀態。
stop,suspend,resume,destroy: 這些方法都已經失效了。
InterruptedException
InterruptedException
,應該使用Thread.currentThread().interrupt()
將中斷標識回覆爲true,而後在該層拋出異常。將中斷標識設爲true很重要,它使得異常在能夠在更高的層次上進行處。Threads能夠設置UncaughtExceptionHandler
,它會在程序忽然中斷的時候收到通知。函數
Thread thread = new Thread(runnable); thread.setUncaughtExceptionHandler((failedThread, exception) -> { logger.error("Caught unexpected exception in thread '{}'.", failedThread.getName(), exception); }); thread.start();
當多個線程在等待彼此釋放持有的資源,從而造成了資源佔有和等待的循環時,就產生了死鎖。可能產生死鎖的例子:this
class Account { private long amount; void plus(long amount) { this.amount += amount; } void minus(long amount) { if (this.amount < amount) throw new IllegalArgumentException(); else this.amount -= amount; } static void transferWithDeadlock(long amount, Account first, Account second){ synchronized (first) { synchronized (second) { first.minus(amount); second.plus(amount); } } } }
死鎖可能會這樣產生:spa
避免死鎖的方法有:
class Account { private long id; private long amount; // Some methods are omitted static void transferWithLockOrdering(long amount, Account first, Account second){ boolean lockOnFirstAccountFirst = first.id < second.id; Account firstLock = lockOnFirstAccountFirst ? first : second; Account secondLock = lockOnFirstAccountFirst ? second : first; synchronized (firstLock) { synchronized (secondLock) { first.minus(amount); second.plus(amount); } } } }
class Account { private long amount; // Some methods are omitted static void transferWithTimeout( long amount, Account first, Account second, int retries, long timeoutMillis ) throws InterruptedException { for (int attempt = 0; attempt < retries; attempt++) { if (first.lock.tryLock(timeoutMillis, TimeUnit.MILLISECONDS)) { try { if (second.lock.tryLock(timeoutMillis, TimeUnit.MILLISECONDS)) { try { first.minus(amount); second.plus(amount); } finally { second.lock.unlock(); } } } finally { first.lock.unlock(); } } } } }
當全部的線程都在協商對資源的訪問,或是預防死鎖,從而致使沒有一個線程真正在運行時,會產生活鎖。當一個線程長時間佔據一個鎖致使別的線程沒法進展時,會產生線程飢餓現象。
線程池的核心接口是ExecutorService
,java.util.concurrent
還提供了一個靜態工廠Executors
,它包含建立具備最多見配置的線程池的工廠方法。
工廠方法以下:
newSingleThreadExecutor: 返回一個只有一個線程的ExecutorService
newFixedThreadPool: 返回一個具備固定數目線程的ExecutorService
newCachedThreadPool: 返回一個可變大小的線程池ExecutorService
newSingleThreadScheduledExecutor: 返回只有一個線程的ScheduledExecutorService
newScheduledThreadPool: 返回包含一組線程的ScheduledExecutorService
newWorkStealingPool: 返回一個帶有並行級別的ExecutorService
當調整線程池大小時,最好基於機器運行該應用時分配的邏輯內核數。能夠經過調用
Runtime.getRuntime().availableProcessors()
來得到該值。
線程池的實現類
任務經過ExecutorService#submit
,ExecutorService#invokeAll
或ExecutorService#invokeAny
提交,它們對不一樣類型的任務有多種重載。
任務的功能性接口:
Runnable: 表示一個沒有返回值的任務
Callable: 表示一個包含返回值的計算。它還聲明能夠拋出原始異常,因此不須要對檢查異常進行包裝
Future
是對全部的異步計算的抽象。它表示這些計算的結果,在某些時候可用。大多數的ExecutorService
方法都是用Future
做爲返回值。它包含檢查當前future的狀態以及阻塞當前讀取操做直至結果能夠被讀取等方法。
ExecutorService executorService = Executors.newSingleThreadExecutor(); Future<String> future = executorService.submit(() -> "result"); try { String result = future.get(1L, TimeUnit.SECONDS); System.out.println("Result is '" + result + "'."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e.getCause()); } catch (TimeoutException e) { throw new RuntimeException(e); } assert future.isDone();
Lockjava.util.concurrent.locks
包中有一個標準的Lock
接口,ReentrantLock
實現複製了synchronized
關鍵字的功能,同時提供了一些額外的功能,好比獲取當前鎖狀態的信息,非阻塞的tryBlock()
方法,以及可中斷的鎖。下面是使用具體的ReentrantLock
實例的例子:
class Counter { private final Lock lock = new ReentrantLock(); private int value; int increment() { lock.lock(); try { return ++value; } finally { lock.unlock(); } } }
ReadWriteLockjava.util.concurrent.locks
包還包含了ReadWriteLock
接口(以及ReentrantReadWriteLock
實現),它被定義爲一組讀寫鎖,支持多個同步讀者和單一寫者。
class Statistic { private final ReadWriteLock lock = new ReentrantReadWriteLock(); private int value; void increment() { lock.writeLock().lock(); try { value++; } finally { lock.writeLock().unlock(); } } int current() { lock.readLock().lock(); try { return value; } finally { lock.readLock().unlock(); } } }
CountDownLatchCountDownLatch
經過一個數值初始化。線程會調用await()
方法阻塞本身,等待計數值爲0後再繼續運行。其它的線程(或是同一個線程)調用countDown()
來減小計數。一旦計數爲0後,該倒計時器便不能夠重複使用。用來在達到某個條件後,啓動一組未知數量的線程
CompletableFutureCompletableFuture
是異步計算的一個抽象。不一樣於Future
,只能經過阻塞獲取結果,該類鼓勵註冊回調函數來建立一組任務,從而在獲得返回值或是出現異常時執行該任務。
想要了解更多開發技術,面試教程以及互聯網公司內推,歡迎關注個人微信公衆號!將會不按期的發放福利哦~