Part I: Fundamentals原理 Chapter 2. Thread Safety

--注:由於Java Concurrency in Practice時間久遠業已脫銷,所以本人對讀到的一些要點進行整理,主要用來個人進一步深化學習,書中原文會加中英文註釋,自己的白話僅有中文。

Chapter 2. Thread Safety線程安全

If multiple threads access the same mutable state variable without appropriate synchronization, your program is broken. There are three ways to fix it:
如果多個線程在沒有適當的同步機制情況下訪問同一個狀態易變的變量, 那麼你的程序容易被打破。下面有三種解決方式:

Don't share the state variable across threads;
不要在多個線程間共享狀態變量;

Make the state variable immutable; or
保持狀態變量不可變;

Use synchronization whenever accessing the state variable.
當併發訪問狀態變量時候注意使用同步機制(同步代碼塊或鎖);

2.1. What is Thread Safety? 什麼是線程安全

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.

一個類如果被多個線程訪問且運行正常那麼他是安全的,無論被運行環境如何安排或交錯那些線程執行,並且沒有在被調用代碼塊添加任何其他同步機制或其他協調方式。

2.1.1. Example: A Stateless Servlet


@ThreadSafe
public class StatelessFactorizer implements Servlet {
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}


以上例子符合保持線程安全的三種方式。

2.2. Atomicity原子性

Listing 2.2. Servlet that Counts Requests without the Necessary Synchronization. Don't Do this.


@NotThreadSafe
public class UnsafeCountingFactorizer implements Servlet {
private long count = 0;

public long getCount() { return count; }

public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
++count;//非原子操作
encodeIntoResponse(resp, factors);
}

}


以上程序是對count進行計算,需要對其進行read+modify+write的操作,在多個線程併發訪問程序的時候,如果某個線程在read+modify+write三步操作的任何一步阻塞,則可能會造成數據的不同步,進而影響數據真實性。那麼,這裏管read+modify+write三步操作爲不可拆分的操作,只有read+modify+write三步操作同時成功執行,才能保證數據真實性,那麼三個操作構成了[b]原子操作[/b]。
2.2.1. Race Conditions競爭狀況

有競爭纔會涉及併發訪問。

2.2.2. Example: Race Conditions in Lazy Initialization

Listing 2.3. Race Condition in Lazy Initialization. Don't Do this.


@NotThreadSafe
public class LazyInitRace {
private ExpensiveObject instance = null;

public ExpensiveObject getInstance() {
if (instance == null)//多個線程訪問時候,如果某個線程阻塞,那麼這個線程重新搶佔時間後它讀到的數據可能就是髒數據了
instance = new ExpensiveObject();
return instance;
}
}


2.2.3. Compound Actions複合行爲(操作)

上面說了read+modify+write爲一個不可拆分的原子操作,而其實質上還是三個操作,將這三個操作在程序級加上同步機制使其構成一個原子操作,我們指出這三個操作就是一個複合操作。那麼在java的api也提供了一些原子操作工具類,來替代顯示的synchronize這些標識。

@ThreadSafe
public class CountingFactorizer implements Servlet {
private final AtomicLong count = new AtomicLong(0);//可以用原子方式更新的 long 值。

public long getCount() { return count.get(); }

public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
count.incrementAndGet();
encodeIntoResponse(resp, factors);
}
}


[img]http://dl.iteye.com/upload/attachment/330591/d473f23b-f3a9-37ad-8376-b58810f85f55.jpg[/img]

上面圖片中列出了軟件包 java.util.concurrent.atomic(類的小工具包,支持在單個變量上解除鎖定的線程安全編程。 ) 中的支持同步更新的線程安全類。

2.3. Locking 鎖

2.3.1. Intrinsic Locks 內在鎖

synchronized (lock) {
// Access or modify shared state guarded by lock
}

2.3.2. Reentrancy 重入

當一個鎖被某個線程持有,那麼其他線程不可再獲取這把鎖,但是持有這把鎖的線程可以重新持有。其實在運行時環境中有一個對同步鎖計數的機制,即鎖空閒態時候,count=0,當某個線程持有這把鎖後count++,並記錄這把鎖的owner,當持有這把鎖的線程再次持有這把鎖後繼續count++。

Listing 2.7. Code that would Deadlock if Intrinsic Locks were Not Reentrant.


public class Widget {
public synchronized void doSomething() {
...
}
}

public class LoggingWidget extends Widget {
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
//比如程序運行時,某個線程執行到這裏阻塞的話,沒有重新進入super.doSomething(),那麼即造成死鎖。
super.doSomething();
}
}


2.4. Guarding State with Locks 用鎖保持狀態

如何正確使用鎖機制,要根據實際情況有一個合適的力度,不能隨隨便的就把某個方法直接synchronized(可能將不需要同步的部分也加鎖,那麼對性能造成了一定的影響),也不能將read-modify-write的某一個或者兩個操作放入原子操作(沒有正確分析原子操作構成)。

2.5. Liveness and Performance 活性與性能

Listing 2.8. Servlet that Caches its Last Request and Result.


@ThreadSafe
public class CachedFactorizer implements Servlet {
@GuardedBy("this") private BigInteger lastNumber;
@GuardedBy("this") private BigInteger[] lastFactors;
@GuardedBy("this") private long hits;
@GuardedBy("this") private long cacheHits;

public synchronized long getHits() { return hits; }
public synchronized double getCacheHitRatio() {
return (double) cacheHits / (double) hits;
}

public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = null;
synchronized (this) {
++hits;
if (i.equals(lastNumber)) {
++cacheHits;
factors = lastFactors.clone();
}
}
if (factors == null) {
factors = factor(i);
synchronized (this) {
lastNumber = i;
lastFactors = factors.clone();
}
}
encodeIntoResponse(resp, factors);
}
}


以上例子,正式遵循保證原子操作,又不濫用原子操作,保證程序的數據可靠與性能良好。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章