java多线程之线程的安全性(一)
對象的狀態:對象的狀態是指存儲在狀態變量(實例或靜態域)中的數據。對象的狀態還可能包括其他依賴對象的域。例如,HashMap的狀態不僅儲存在對象本身,還儲存在Map.Entry對象中。
多線程安全的概念:當多個線程訪問某個類時,不管運行時環境采用何種調度方式或者這些線程將如何交替執行,并且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行為,那么這個類就是線程安全的。
多線程安全的核心:編寫線程安全的代碼,核心在于要對狀態訪問操作進行管理,特別是對共享的(Shared)和可變的(Mutable)狀態的訪問。
?
1.無狀態對象線程安全
如例子中的因式分解Servlet:
public class StatelessFactorizer implement Servlet{public void service(ServletRequest req,ServletResponse resp){BigInteger i = extractFromRequest(req);BingInteger []factorys = factor(i);encodeingIntoResponse(req,factorys);} }它既不包含任何域,也不包含其他類中域的引用,所以他是無狀態的。因此每個線程執行的結果不會影響到其他線程。
?
2.盡可能使用現有的線程安全對象管理狀態
下面是訪問一次count+1的例子:
public class UnsafeCountingFactorizer implement Servlet{private long count = 0;public long getCount(){return count;}public void service(ServletRequest req,ServletResponse resp){count++;BigInteger i = extractFromRequest(req);BingInteger []factorys = factor(i);encodeingIntoResponse(req,factorys);} }count++的操作是 讀取count的值 count+1 寫入count 三步,如果線程1執行到第二步還沒寫入count的時候,線程2開始執行了第一步,那么線程2讀取的count是線程1還沒賦值的count,于是就出現的線程安全問題。
下面是解決方法:
通過用AtomicLong來代替long類型的計數器,能夠確保所有對計數器狀態的訪問操作都是原子的。由于Servlet的狀態只有一個,也就是計數器的狀態,所以這個Servlet是線程安全的。
3.防止競態條件的出現
競態條件:由于不恰當的執行時序而出現不正確的結果。常見的情況是延遲初始化。
public class LazyInitRace{private ExpensiveObject instance = null;public ExpensiveObject getInstance(){if(instance == null){instance = new ExpensiveObject();}return instance;} }上面的競態條件很容易出現線程安全問題,如果線程1和2同時進入if語句,那么這個單例模式不能達到它想要的效果。
下面是解決方法:
這叫雙重鎖,他能保證new只會執行一次,保證了執行順序,所以競態條件就不存在了。而且我們要保證這個鎖是同一個鎖。
4.對于多個變量的不變性條件,涉及的所有變量需要同一個鎖保護。
下面的例子是緩存因式分解結果,如果傳入值相同,返回緩存值。
public class UnSafeCountingFactorizer implement Servlet{//最后一次傳入的值private final AtomicReferece<BigInteger> lastNumber = new AtomicReference<Integer>();//最后一次傳入的緩存private final AtomicReferece<BigInteger[]> lastFactors = new AtomicReference<Integer[]>();public void service(ServletRequest req,ServletResponse resp){BigInteger i = extractFromRequest(req);if(i.equals(lastNumber)){encodeingIntoResponse(req,factorys);}else{BingInteger []factorys = factor(i);lastFactors.set(factors);lastNumber.set(i);encodeingIntoResponse(req,factorys);}} }上面的例子,兩個狀態都是能保證原子性的,但是不變性條件同時包含兩個狀態,也就是只修改其中一個變量,那么在兩次修改操作之間,其他線程將發現不變性條件被破壞了。
解決方法:
重新構造了這個類之后,實現了簡單性和并發性的平衡,把同步代碼全部方法放在一個Synchronized可能會影響效率,而將同步代碼塊分解太小會影響簡單行和可讀性。
5.占資源太多的操作不要持有鎖
例如,當一個程序執行時間太長或者占有cpu資源太大,很可能會受到其他因素的影響,導致長時間持有鎖,導致其他進程長時間發生阻塞。
總結
以上是生活随笔為你收集整理的java多线程之线程的安全性(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring之SpringMVC(四)
- 下一篇: 容器源码分析之ArrayList(二)