一,谈谈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多线程发展简史