参考资料:

http://ifeve.com/java-memory-model-4/

http://www.infoq.com/cn/articles/java-memory-model-1

http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/

https://en.wikipedia.org/wiki/Singleton_pattern#Java_5_solution

https://www.ibm.com/developerworks/java/library/j-jtp06197/

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 = newSingleton(),这时两个线程得到了两个不同的对象,与预期不符。

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维护则可以完美避免此问题

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
* @Description: 测试ThreadLocal
*/
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(String date) throws InterruptedException {
multilpleThreadExecute(
new Runnable() {
@Override
public void run() {
try {
System.out.println(df.parse(date));
}
catch (ParseException e) {
}
}
});
}

private static void testThreadLocal(String date) throws InterruptedException {
multilpleThreadExecute(
new Runnable() {
@Override
public void run() {
try {
System.out.println(DATE_FORMAT.get().parse(date));
}
catch (ParseException e) {
}
}
});
}

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);
}
}

更多相关文章

  1. Java的Grizzly为缓冲区占用了大量内存?
  2. java线程池深入二
  3. java基础---JVM---调优,垃圾收集器,内存分配策略
  4. JavaScript基础——变量、作用域和内存问题
  5. Java Executor多线程框架
  6. java线程--volatile实现可见性
  7. Maven:主线程中的NoClassDefFoundError
  8. java线程池使用场景和使用方法较详细文摘
  9. java中多线程安全问题产生&解决方案——同步方法

随机推荐

  1. 为什么通过JavaScript更改样式会受到CSS
  2. 对jquery的 attr()和prop()理解
  3. JavaScript中,提取子字符串方法:Slice、S
  4. 【JavaScript】离线应用与客户端存储
  5. javascript入门笔记(1)——变量和计算
  6. js关于Function.prototype.bind
  7. RichFaces 3.3.3 和 JSF 2.0
  8. js中ajax获取json数据遍历提示undefined
  9. 在单选按钮上选中/取消选中,加载/隐藏部分
  10. javascript 的MD5代码备份,跟java互通