最近想將java基礎的一些東西都整理整理寫下來這是對知識的總結也是一種樂趣已經擬好了提綱大概分為這幾個主題 java線程安全java垃圾收集java並發包詳細介紹java profile和jvm性能調優慢慢寫吧本人jameswxx原創文章轉載請注明出處我費了很多心血多謝了關於java線程安全網上有很多資料我只想從自己的角度總結對這方面的考慮有時候寫東西是很痛苦的知道一些東西但想用文字說清楚卻不是那麼容易我認為要認識java線程安全必須了解兩個主要的點java的內存模型java的線程同步機制特別是內存模型java的線程同步機制很大程度上都是基於內存模型而設定的後面我還會寫java並發包的文章詳細總結如何利用java並發包編寫高效安全的多線程並發程序暫時寫得比較倉促後面會慢慢補充完善
淺談java內存模型
不同的平台內存模型是不一樣的但是jvm的內存模型規范是統一的其實java的多線程並發問題最終都會反映在java的內存模型上所謂線程安全無非是要控制多個線程對某個資源的有序訪問或修改總結java的內存模型要解決兩個主要的問題可見性和有序性我們都知道計算機有高速緩存的存在處理器並不是每次處理數據都是取內存的JVM定義了自己的內存模型屏蔽了底層平台內存管理細節對於java開發人員要清楚在jvm內存模型的基礎上如果解決多線程的可見性和有序性
那麼何謂可見性?多個線程之間是不能互相傳遞數據通信的它們之間的溝通只能通過共享變量來進行Java內存模型(JMM)規定了jvm有主內存主內存是多個線程共享的當new一個對象的時候也是被分配在主內存中每個線程都有自己的工作內存工作內存存儲了主存的某些對象的副本當然線程的工作內存大小是有限制的當線程操作某個對象時執行順序如下
()從主存復制變量到當前工作內存(read and load)
()執行代碼改變共享變量值(use and assign)
()用工作內存數據刷新主存相關內容(store and write)
JVM規范定義了線程對主存的操作指令readloaduseassignstorewrite當一個共享變量在多個線程的工作內存中都有副本時如果一個線程修改了這個共享變量那麼其他線程應該能夠看到這個被修改後的值這就是多線程的可見性問題
那麼什麼是有序性呢?線程在引用變量時不能直接從主內存中引用如果線程工作內存中沒有該變量則會從主內存中拷貝一個副本到工作內存中這個過程為readload完成後線程會引用該副本當同一線程再度引用該字段時有可能重新從主存中獲取變量副本(readloaduse)也有可能直接引用原來的副本(use)也就是說 readloaduse順序可以由JVM實現系統決定
線程不能直接為主存中中字段賦值它會將值指定給工作內存中的變量副本(assign)完成後這個變量副本會同步到主存儲區(storewrite)至於何時同步過去根據JVM實現系統決定有該字段則會從主內存中將該字段賦值到工作內存中這個過程為readload完成後線程會引用該變量副本當同一線程多次重復對字段賦值時比如
Java代碼
for(int i=i<i++)
a++線程有可能只對工作內存中的副本進行賦值只到最後一次賦值後才同步到主存儲區所以assignstoreweite順序可以由JVM實現系統決定假設有一個共享變量x線程a執行x=x+從上面的描述中可以知道x=x+並不是一個原子操作它的執行過程如下
從主存中讀取變量x副本到工作內存給x加 將x加後的值寫回主 存
如果另外一個線程b執行x=x執行過程如下
從主存中讀取變量x副本到工作內存給x減 將x減後的值寫回主存
那麼顯然最終的x的值是不可靠的假設x現在為線程a加線程b減從表面上看似乎最終x還是為但是多線程情況下會有這種情況發生
線程a從主存讀取x副本到工作內存工作內存中x值為 線程b從主存讀取x副本到工作內存工作內存中x值為 線程a將工作內存中x加工作內存中x值為 線程a將x提交主存中主存中x為 線程b將工作內存中x值減工作內存中x值為 線程b將x提交到中主存中主存中x為
同樣x有可能為如果x是一個銀行賬戶線程a存款線程b扣款顯然這樣是有嚴重問題的要解決這個問題必須保證線程a和線程b是有序執行的並且每個線程執行的加或減是一個原子操作看看下面代碼
Java代碼
public class Account {
private int balance
public Account(int balance) { thisbalance = balance}
public int getBalance() { return balance}
public void add(int num) { balance = balance + num}
public void withdraw(int num) { balance = balance num}
public static void main(String[] args) throws InterruptedException { Account account = new Account()Thread a = new Thread(new AddThread(account ) add)Thread b = new Thread(new WithdrawThread(account ) withdraw)astart()bstart()ajoin()bjoin()Systemoutprintln(accountgetBalance())}
static class AddThread implements Runnable { Account accountint amount
public AddThread(Account account int amount) { thisaccount = accountthisamount = amount}
public void run() { for (int i = i < i++) { accountadd(amount)}
static class WithdrawThread implements Runnable { Account accountint amount
public WithdrawThread(Account account int amount) { thisaccount = accountthisamount = amount}
public void run() { for (int i = i < i++) { accountwithdraw(amount)}第一次執行結果為第二次執行結果為每次執行的結果都是不確定的因為線程的執行順序是不可預見的這是java同步產生的根源synchronized關鍵字保證了多個線程對於同步塊是互斥的synchronized作為一種同步手段解決java多線程的執行有序性和內存可見性而volatile關鍵字之解決多線程的內存可見性問題後面將會詳細介紹
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26952.html