java_JUC、volatile

JUC:

  指java中java.util.concurrent包;(concurrent:并发)

 

JUC分类:

  java.util.concurrent

  java.util.concurrent.atomic(atomic:原子性)

  java.util.concurrent.locks(locks:锁

 


1、volatile是什么?

  volatile是java虚拟机提供的轻量级同步机制;

    三大特性:

      保证可见性;

      不保证原子性;

      禁止指令重排;

JVM(java虚拟机)

JMM(java内存模型):

  Java Memory Model本身是有一种抽象的概念并不真实存在的;

  它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段、构成数组对象的元素等)的访问方式;

  三大特性:

    可见性;

    原子性;

    有序性;

 

什么是JMM内存模型的可见性?

JMM关于同步的规定:

  1、线程解锁前必须把共享变量的值刷新回主内存;

  2、线程加锁前必须读取主内存的最新值到自己的工作内存;

  3、加锁与解锁是同一把锁;

 

由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间);工作内存是每个线程的私有数据区域,

而java内存模型中规定所有变量都存储在主内存,主内存是共享的内存区域,所有线程都可以访问;但线程对变量的操作必须在工作内存中进行,

首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存;不能直接操作主内存的变量,各个线程中

的工作内存存储着主内存的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信必须通过主内存来完成;

 

JMM内存模型之可见性:线程A与B同时拷贝主内存中的变量,当A操作完成,将操作后的值写进主内存时,将第一时间通知B,B将第一时间知道主内存中的变量值发改变;

volatile可见性代码验证如下:

class MyData{
    int number = 0;
    public void addTo60(){
        this.number = 60;
    }
}

/**
 * 1、验证Volatile的可见性
 *  1.1、假如int number = 0;,number变量之前根本没有添加volatile关键字修饰,不可见性
 */
public class VolatileDemo {
    public static void main(String[] args) {
        MyData myData = new MyData();//资源类

        new Thread(() -> {//第一个线程A
            System.out.println(Thread.currentThread().getName()+"\t come in");
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            myData.addTo60();
            System.out.println(Thread.currentThread().getName()+"\t updated number value: "+myData.number);
        },"A").start();

        //第二个线程main
        while (myData.number == 0){
            //main线程就一直在这里等待循环,知道number值不再为0
        }
        System.out.println(Thread.currentThread().getName()+"\t mission is over,main get number value:"+myData.number);

    }
}

添加volatile关键字后

volatile int number = 0;

原子性:不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者分割,需要整体完整,要么同时成功,要么同时失败;

volatile不保证原子性代码验证如下:

class MyData{
    volatile int number = 0;
    public void addTo60(){
        this.number = 60;
    }
    public void addPlusPlus(){
        number++;
    }
}

/**
 * 1、验证volatile的可见性
 *  1.1、假如int number = 0;,number变量之前根本没有添加volatile关键字修饰,不可见性
 *  1.2、添加了volatile关键字,可以解决可见性问题;
 *
 * 2、验证volatile的不保证原子性
 *  2.1、原子性指的是什么意思?
 *      不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者分割,
 *      需要整体完整,要么同时成功,要么同时失败;
 */
public class VolatileDemo {
    public static void main(String[] args) {
        MyData myData = new MyData();//资源类
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    myData.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }

        //需要等待上面20个线程都计算完成,再用main线程取得最终的结果
        while (Thread.activeCount() > 2){//后台默认存在的两个线程,main、GC
            Thread.yield();//等待
        }
        System.out.println(Thread.currentThread().getName()+"\t finally number value:"+myData.number);
    }
}

volatile解决原子性问题?

  使用synchronized关键字(大材小用,不推荐);

  使用JUC下的AtomicInteger类;

class MyData{
    volatile int number = 0;
    public void addTo60(){
        this.number = 60;
    }
    public void addPlusPlus(){
        number++;
    }
    AtomicInteger atomicInteger = new AtomicInteger();//保证原子性的整型封装类
    public void addMyAtomic(){
        atomicInteger.getAndIncrement();//获取值并且+1
    }
}

/**
 * 1、验证volatile的可见性
 *  1.1、假如int number = 0;,number变量之前根本没有添加volatile关键字修饰,不可见性
 *  1.2、添加了volatile关键字,可以解决可见性问题;
 *
 * 2、验证volatile的不保证原子性
 *  2.1、原子性指的是什么意思?
 *      不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者分割,
 *      需要整体完整,要么同时成功,要么同时失败;
 *  2.2、volatile不保证原子性
 *  2.3、why
 *  2.4、如何解决原子性问题?
 *      *加synchronized关键字(大材小用,不推荐)
 *      *使用JUC下的AtomicInteger类
 */
public class VolatileDemo {
    public static void main(String[] args) {
        MyData myData = new MyData();//资源类
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    myData.addPlusPlus();
                    myData.addMyAtomic();
                }
            },String.valueOf(i)).start();
        }

        //需要等待上面20个线程都计算完成,再用main线程取得最终的结果
        while (Thread.activeCount() > 2){//后台默认存在的两个线程,main、GC
            Thread.yield();//等待
        }
        System.out.println(Thread.currentThread().getName()+"\t finally number value:"+myData.number);
        System.out.println(Thread.currentThread().getName()+"\t finally atomicInteger value:"+myData.atomicInteger);
    }
}

JMM有序性(禁止指令重排):

volatile禁止指令重排:

 

线程安全性问题获得保障:

 

单例模式在多线程中的问题:

public class SingletonDemo2 {
    public static SingletonDemo2 instance = null;
    private SingletonDemo2(){
        System.out.println("SingletonDemo2");
    }
    public static SingletonDemo2 getInstance(){
        if (instance == null){
            instance = new SingletonDemo2();
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(() ->{
                getInstance();
            },String.valueOf(i)).start();
        }
    }
}

直接使用synchronized关键字:

public static synchronized SingletonDemo2 getInstance(){//大材小用,不合适
        if (instance == null){
            instance = new SingletonDemo2();
        }
        return instance;
    }

使用DCL(Double Check Lock 双端检索机制):

    //DCL(双端检索机制)
    public static SingletonDemo2 getInstance(){//由于指令重排机制,不一定100%线程安全
        if (instance == null){
            synchronized (SingletonDemo2.class){
                if (instance == null){
                    instance = new SingletonDemo2();
                }
            }
        }
        return instance;
    }

 

单实例volatile分析:

 

使用volatile关键字解决DCL安全性问题:

public static volatile SingletonDemo2 instance = null;
public class SingletonDemo2 {
    public static volatile SingletonDemo2 instance = null;
    private SingletonDemo2(){
        System.out.println("SingletonDemo2");
    }
    //DCL(双端检索机制)
    public static SingletonDemo2 getInstance(){//由于指令重排机制,不一定100%线程安全
        if (instance == null){
            synchronized (SingletonDemo2.class){
                if (instance == null){
                    instance = new SingletonDemo2();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(() ->{
                getInstance();
            },String.valueOf(i)).start();
        }
    }
}

 

posted @ 2021-02-25 18:01  DHaiLin  阅读(87)  评论(0编辑  收藏  举报