做者:享學課堂James老師java
轉載請聲明出處!編程
接着第1篇後,咱們繼續來跟進一下併發編程的其它內容,以下:安全
要安全的發佈一個對象,對象的引用和對象的狀態必須同時對其餘線程可見。通常一個正確構造的對象(構造函數不發生this操做),能夠經過以下方式來正確發佈:bash
class JamesStaticInitializer {
// 在不進行其餘初始化的狀況下發布不可變對象
public static final Year year = Year.of(2017);
public static final Set<String> keywords;
// 使用靜態static來構造複雜對象
static {
// 建立可變集
Set<String> keywordsSet = new HashSet<>();
// 初始化狀態
keywordsSet.add("james");
keywordsSet.add("13號技師");
// 使集合不可修改
keywords = Collections.unmodifiableSet(keywordsSet);
}
}
複製代碼
class JamesVolatile {
private volatile String state;
void setState(String state) {
this.state = state;
}
String getState() {
return state;
}
}
複製代碼
class JamesAtomics {
private final AtomicInteger state = new AtomicInteger();
void initializeState(int state) {
this.state.compareAndSet(0, state);
}
int getState() {
return state.get();
}
}
複製代碼
class JamesFinal {
private final String state;
JamesFinal(String state) {
this.state = state;
}
String getState() {
return state;
}
}
複製代碼
確保此引用在構造期間不逃逸。多線程
this引用逃逸("this"escape)是指對象尚未構造完成,它的this引用就被髮布出去了。這是危及到線程安全的,由於其餘線程有可能經過這個逸出的引用訪問到「初始化了一半」的對象(partially-constructed object)。這樣就會出現某些線程中看到該對象的狀態是沒初始化完的狀態,而在另一些線程看到的倒是已經初始化完的狀態,這種不一致性是不肯定的,程序也會所以而產生一些沒法預知的併發錯誤。在說明併發編程中如何避免this引用逸出以前併發
class JamesThisEscapes {
private final String name;
ThisEscapes(String name) {
JamesCache.putIntoCache(this);
this.name = name;
}
String getName() {
return name;
}
}
class JamesCache {
private static final Map<String, ThisEscapes> CACHE = new ConcurrentHashMap<>();
static void putIntoCache(JamesThisEscapes thisEscapes) {
//「this」引用在對象徹底構造以前逃逸
CACHE.putIfAbsent(thisEscapes.getName(), thisEscapes);
}
}
複製代碼
class JamesSynchronization {
private String state;
synchronized String getState() {
if (state == null)
state = "Initial";
return state;
}
}
複製代碼
不可變對象具有執行安全的特性。此外,相較於可變對象,不可變對象一般也較合理,易於瞭解,並且提供較高的安全性。不可變對象的一個重要特性是它們都是線程安全的,所以不須要同步。固然對象不可變的是有以下要求滴:函數
final.
this
在構造方法執行期間引用不會逃脫。不可變對象的示例:post
// 聲明爲final類
public final class JamesArtist {
// 不可變對象, 字段爲final
private final String name;
//用於保存不可變對象, final類型
private final List<JamesTrack> tracks;
public JamesArtist(String name, List<JamesTrack> tracks) {
this.name = name;
//防止拷貝
List<JamesTrack> copy = new ArrayList<>(tracks);
//標記爲不可更改
this.tracks = Collections.unmodifiableList(copy);
// 「this」在構造期間不會傳遞到任何地方。
}
}
// 同上聲明爲final類
public final class JamesTrack {
// 不可變對象, 字段爲final
private final String title;
public JamesTrack(String title) {
this.title = title;
}
}
複製代碼
java.lang.Thread
類用於表示應用程序或JVM線程。代碼老是在某些Thread類的上下文中執行(用於 Thread#currentThread()
獲取本身的Thread)。ui
線程狀態以下this
線程協調方法以下
InterruptedException.
InterruptedException
,則應經過調用將中斷的標誌恢復爲true, Thread.currentThread().interrupt()
而且應該拋出一個更合適的異常。將標誌設置爲true很是重要,以便有機會處理更高級別的中斷。線程能夠指定 UncaughtExceptionHandler
將接收任何致使線程忽然終止的未捕獲異常的通知。
Thread thread = new Thread(runnable);
thread.setUncaughtExceptionHandler((failedThread,exception)->
{
logger.error("Caught unexpected exception in thread ‘{}’.", failedThread.getName(), exception);
}
);
thread.start();
複製代碼
當存在多個線程時會發生死鎖,每一個線程等待另外一個線程持有的資源,從而造成資源循環和獲取線程。
潛在的死鎖示例:
class JamesAccount {
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, JamesAccount first, JamesAccount second) {
synchronized (first) {
synchronized (second) {
first.minus(amount);
second.plus(amount);
}
}
}
}
複製代碼
若是同時發生死鎖:
避免死鎖的技巧:
class JamesAccount {
private long id;
private long amount;
// 此處省略了一些方法
static void transferWithLockOrdering(long amount, JamesAccount first, JamesAccount 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 JamesAccount {
private long amount;
//省略了一些方法
static void transferWithTimeout(long amount, JamesAccount first, JamesAccount 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();
}
}
}
}
}
複製代碼
JVM能夠檢測監視器死鎖,並在線程轉儲中打印死鎖信息。
活鎖偏偏與死鎖的概念相反,死鎖的意思是全部線程都拿不到資源且都佔用着對方的資源,而活鎖的意思是拿到資源後各線程卻又相互釋放不執行。
若是多線程中出現了相互謙讓,至關於13號技師得病後你們都不要她了,都主動將資源釋放給其它的線程來使用,那麼這個資源會不斷在多個線程之間跳動但又得不到明確執行,這就是活鎖;並且多線程執行中有線程的優先級,有的線程優先級高它是可以插隊並優先執行的,但若是這些優先級高的線程一直搶佔優先級低線程的資源,最終會致使低優先級線程它是沒法獲得執行的,這就是飢餓。
(未完待續......)
關注我,等待更新,還有更多技術乾貨分享~