zoukankan      html  css  js  c++  java
  • 一,谈谈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 多线程与并发编程专题

  • 相关阅读:
    Universal USB Installer集开源软件之佳作
    利用sdkman安装kotlin和java环境
    centos 7 安装docker
    CentOS 7 安装中文环境
    Using ADB and fastboot
    LinearGradient线型渐变效果
    将头图片变成圆形简单实现
    望远镜效果
    BitmapShader填充图形
    给图片加阴影效果简单示例代码实现
  • 原文地址:https://www.cnblogs.com/springsource/p/2854769.html
Copyright © 2011-2022 走看看