zoukankan      html  css  js  c++  java
  • 【Java多线程系列八】volatile和ThreadLocal

    1. volatile

    final class Singleton {
        private static Singleton instance = null;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }

    以上代码尝试实现单例模式,但存在严重的线程安全风险。Java Memory Model定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。假设Thread1/Thread2并发,instance为它们的共享变量,Thread1与Thread2之间通信必须要经历下面2个步骤:

    • Thread1把本地内存更新过的instance刷新到主内存中去
    • Thread2到主内存中去读取Thread1之前已更新过的instance

    那么可能的场景之一——Thread1执行完instance = new Singleton(),但刷新到主内存前Thread2的instance == null仍然成立,于是再次执行instance = new Singleton(),这时两个线程得到了两个不同的对象,与预期不符。

    final class Singleton {
        private static Singleton instance = null;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

    加入锁和双重校验后,仍然存在风险,因为为了提高性能,编译器和处理器常常会对指令做重排序,以Singleton instance = new Singleton()为例,它包含了三个指令:

    • ①为instance分配内存
    • ②调用Singleton构造方法
    • ③把instance指向分配的内存地址

    三个指令执行顺序可能是①②③或①③②,在③执行之后,instance==null将不再成立。可能的场景——假设Thread1/Thread2并发,Thread1执行了除②以外的指令,Thread2的instance==null不成立,虽然得到了内存地址,但由于未调用构造方法而报错。

    final class Singleton {
        private static volatile Singleton instance = null;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

    为instance变量加上volatile关键字彻底解决问题。volatile的特性:

    • volatile的变量修改后将立即刷新到主内存,其他线程即可读取到新值
    • 编译器利用内存屏障的概念禁止上述三条指令的重排序,只允许①②③的执行顺序

    由于以上特性使volatile极适用于修饰多线程环境下的状态标识。

    2. ThreadLocal

    当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

    以非线程安全的SimpleDateFormat类为例,在并发运行时会出错,但使用ThreadLocal维护则可以完美避免此问题

    package com.concurrent.test;
    
    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    public class ThreadLocalTest {
        private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        private static final ThreadLocal<DateFormat> DATE_FORMAT = new ThreadLocal<DateFormat>() {
            public DateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd");
            }
        };
        
        
        public static void main(String[] args) throws InterruptedException {
            String date = "2017-07-06";
            testDateFormat(date);
            testThreadLocal(date);
        }
        
        private static void testDateFormat(final String date) throws InterruptedException {
            multilpleThreadExecute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(df.parse(date));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        
        private static void testThreadLocal(final String date) throws InterruptedException {
            multilpleThreadExecute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(DATE_FORMAT.get().parse(date));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
        private static void multilpleThreadExecute(Runnable runnable) throws InterruptedException {
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 10; i++) {
                executorService.execute(runnable);
            }
            executorService.shutdown();
            executorService.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS);
        }
    }

     

  • 相关阅读:
    磁盘挂载自动分区脚本
    docker的私有仓库的搭建
    centos6上安装docker
    docker的本地仓库换成阿里云的镜像仓库
    python中的coding的格式书写形式
    mysql5.6的二进制包安装
    mysql忘记root密码
    Verilog利用$fdisplay命令往文件中写入数据
    Quartus和ISE调用Synplify进行综合的问题
    TMS320C6455 SRIO 实现方案
  • 原文地址:https://www.cnblogs.com/zhaoyibing/p/9629058.html
Copyright © 2011-2022 走看看