一,谈谈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 多线程与并发编程专题

 

posted @ 2013-01-10 14:25  何锦彬  阅读(4445)  评论(0编辑  收藏  举报