一,談談JAVA線程的運行內存模型【JAVA內存模型】


程序,糾集到底就是對內存數據的操作,並把計算的結果持久話. 爭議

JAVA中執行的最小單位是線程.JVM實現了各個CPU,操作系統等的差異. 線程的運行模型最終可以抽象的看成如下:

每一條線程都有自己的work memory, 而且共享一個main memory.

JMM的主要問題如下:

原子性,原子級別的操作,每個線程運行時是相互獨立,包括里面未聲明為volatile的變量都是獨立一份,但會進行work memory 和 main memory的同步;

可見性, 線程間的通訊. 即主內存的變量可見的,把值從work memory同步到main memory 進行線程間的通訊,通過synchronize或者volatile可靠性;

有序性,這里主要針對讀和寫及同步主內存的的有序, 線程的操作一般是

read and load 從主存復制變量到當前工作內存
use and assign  執行代碼,改變共享變量值 
store and write 用工作內存數據刷新主存相關內容

其中read and load和store and write  都是多次運行,取決於JVM

 

1,每一條線程中運行的變量只是從main memory 的copy,存放於緩存之中

常見的並發問題, 在內存中各自加,未同步到主內存,比如

package org.benson.threadgroup;

import java.util.concurrent.CountDownLatch;
/**
 * TODO share the thread memory operation 
 * @author Benson QQ 107966750
 *
 */
class Data{
    public static int count=0;
}
public class TreadTest extends Thread{
    private CountDownLatch countDCH=null;
    public TreadTest(CountDownLatch countDCH) {
        this.countDCH=countDCH;
    }
    @Override
    public void run() {
        for(int i=0;i<10000;i++)
//            synchronized (Data.class) {
                Data.count++;
//            }
        countDCH.countDown();
    }
    
    public static void main(String[] args) throws InterruptedException {
        final int threadSize=10;
        CountDownLatch countDCH=new CountDownLatch(threadSize);
        for(int i=0;i<threadSize;i++)
        new Thread(new TreadTest(countDCH)).start();
        countDCH.await();
        System.out.println(Data.count);
    }
}

輸出

91660

這里由於讀寫沒及時有序的同步到主內存造成,小於預期值100000,釋放同步鎖后正常

當第二線程運行完畢時始終輸出正確計算值,原理看synchronize關鍵詞

2,JAVA調用代碼的次序是無序性的.(如DCL等問題)

一,bad實踐,依靠變量線程通訊,這個不容易重現,但執行了N遍還是正確的,取決於JVM的心情問題,代碼如下

package org.benson.threadgroup;
/**
 * TODO share load variable sort
 * @author Benson QQ107966750
 * 
 */
class Process4SortA{
    public int variableA=0;
    public Process4SortA() {
        variableA=100;
    }
}
class Process4SortB extends Thread{
    public boolean startFlag=false;
    public int variableA;
    public void test4SortRun(){
        //do something another stuff
        variableA=100;
        startFlag=true; //maybe will process this code before the variableA=100
    }
    @Override
    public void run() {
        while(true){
            if(startFlag){
            System.out.println(variableA);
            //do something another stuff
            break;
            }
        }
    }
}
public class TreadTestSort {
    public static void main(String[] args) throws Exception {
        Process4SortB proB=new Process4SortB();
        proB.start();
        proB.test4SortRun();
    }
}

 

代碼輸出可能會等於0. 因為在方法test4SortRun()里,執行的順序是不一定的

二,bad實踐,DCL的問題,這里引出最常見的,一個double checked load(DCL) 問題,也是由於無序造成的

class Foo { 
private Resource res = null ; 
public Resource getResource() { 
if (res == null ) 
res = new Resource(); 
return res; 
} 
}

相關資料很多,其實可以把

res = new Resource();

看成

Resource()

res = Resource地址引用;

這個是正確執行順序,因為無序的原因可能是

res = Resource地址引用;

Resource()

所以其他得到了res!=null,就執行了,構造函數根本沒執行完

三,bad實踐,單例模式

這個和DCL類似 
java Singleton 幾種方式解析(實在找不到原帖了,BS下轉貼不貼出地址的)

當然最后一個volatile的可以,因為volatile每次都是讀到主內存中最新的值


相關關鍵詞

synchronize 
1,獲取並掛起monitor, 
2,從main memory copy最新的值 
3,執行 
4,從work memory copy最新的值到main memory 
5,釋放monitor

 

final

不可變,只能在初始化時賦值,

但非靜態可以在構造方法中賦值,應用時時注意順序,如果下面這樣調用 final變量也成了可變了

用如下代碼證明

package org.benson;
/**
* TODO to share the java load class sort
* @author Benson QQ 107966750
*
*/
class Base{
public Base() {
System.out.println("init base,the variableA is "+((Child)this).variableA);
}
public void display(){
System.out.println("display in base,the variableA is "+((Child)this).variableA);
}
}
class Child extends Base{
final int variableA;
public Child(){
variableA=100;
System.out.println("init child,the variableA is "+this.variableA);
this.display();
}
}
public class Test4Sort {
public static void main(String[] args) {
new Child();
}
}

結果如下

init base,the variableA is 0
init child,the variableA is 100
display in base,the variableA is 100

 這樣final值也成了可變了

JAVA的初始化順序  base and child class adress,static-->base class variable-->base class constructor-->child class variable-->child class constructor

volatile

在線程中,讀和寫都是原子性的,volatile的最佳實踐就是用synchronize加鎖寫(或者用不依賴當前volatile的值的寫),然后讀可以不用鎖,可以保證讀到最新值,類似一個共享鎖的實現

1,保證的是線程的可見性,即每次得到的都是最新值,顯然寫入是不保證的,所以不能做計數器

2,如果一個線程里讀和寫同時發生,它可以保證寫先於讀,比如  a=new instance(); 可以保證先把instance()初始化完畢后賦值給a,返回完整的instance

如一個線程的寫入遠少於,一個線程不停的讀取,就可以對變量進行volatile了,能保證讀到的是最新值(但寫入必須不依賴當前值),這種情況可以考慮和synchronize同時用,參考從ConcurrentHashMap類學習高並發程序的設計思路【深入JDK源碼】

 

參考資料

雙重檢查鎖定及單例模式

Java內存模型(找不到原帖)

Java多線程發展簡史

 

Java 多線程與並發編程專題

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM