【项目实战】多线程环境下正确创建单例

简介: 对项目代码进行扫描时,出现静态扫描严重问题,发现是由于多线程环境下没有正确创建单例所导致

前言


对项目代码进行扫描时,出现静态扫描严重问题,发现是由于多线程环境下没有正确创建单例所导致。


问题分析


本项目使用的JDK 1.7+

项目代码如下(修改了类名,但核心没变)

static class Singleton {
  private static volatile Singleton cache = null;
  private static Object mutexObj = new Object();
  private Singleton() {
  }
  public static Singleton getInstance() {
    Singleton tmp = cache;
      if (tmp == null) {
      synchronized (mutexObj) {
        if (tmp == null) {                    
          tmp = new Singleton();
          cache = tmp;  
        }               
      }
    }
    return tmp;
  }
}

按照项目生成单例代码,使用如下测试类进行测试

public class Test {
  public static void main(String[] args) {  
    for (int i = 0; i < 3; i++) {
      Thread thread = new Thread(new Runnable() {
        public void run() {
          System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString());
        }
      });
      thread.setName("Thread" + i);
      thread.start();
    }
  }
  static class Singleton {
    private static volatile Singleton cache = null;
    private static Object mutexObj = new Object();
    private Singleton() {
    }
    public static Singleton getInstance() {
      Singleton tmp = cache;
        if (tmp == null) {
        synchronized (mutexObj) {
          if (tmp == null) {                    
            tmp = new Singleton();
            cache = tmp;  
          }               
        }
      }
      return tmp;
    }
  }
}

输出结果如下:

Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4

Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4

Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4

从结果看,都生成了同一个实例,似乎不存在问题,多线程环境下确实不太好重现问题,现改动代码如下:

static class Singleton {
  private static volatile Singleton cache = null;
  private static Object mutexObj = new Object();
  private Singleton() {
  }
  public static Singleton getInstance() {
    Singleton tmp = cache;
      if (tmp == null) {
          System.out.println(Thread.currentThread().getName() + " in outer if block");
          synchronized (mutexObj) {
        System.out.println(Thread.currentThread().getName() + " in synchronized block");
              if (tmp == null) {   
                  System.out.println(Thread.currentThread().getName() + " in inner if block");
                  tmp = new Singleton();
                  cache = tmp;  
              } 
              System.out.println(Thread.currentThread().getName() + " out inner if block");
      }
          System.out.println(Thread.currentThread().getName() + " out synchronized block");
      }
      System.out.println(Thread.currentThread().getName() + " out outer if block");
      return cache;
  }
}

上述代码中添加了Thread.sleep(1)这条语句,其中,Thread.sleep(1)进行休眠时,线程不会释放拥有的锁,并且打印了相关的语句,便于查看线程正运行在哪里的状态。

再次测试,输出结果如下:

Thread2 in outer if block

Thread1 in outer if block

Thread0 in outer if block

Thread2 in synchronized block

Thread2 in inner if block

Thread2 out inner if block

Thread2 out synchronized block

Thread0 in synchronized block

Thread2 out outer if block

Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@60b07af1

Thread0 in inner if block

Thread0 out inner if block

Thread0 out synchronized block

Thread1 in synchronized block

Thread0 out outer if block

Thread1 in inner if block

Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@625795ce

Thread1 out inner if block

Thread1 out synchronized block

Thread1 out outer if block

Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@642c39d2

从结果看,生成了3个不同的实例,并且每个线程都执行了完整的流程,并且可知单例的创建存在问题。在分析原因前简单了解下多线程模型,多线程模型如下:

每个线程有自己独立的工作空间,线程间进行通信是通过主内存完成的,想了解详细内容可参见如下链接:内存模型深入理解java内存模型

知道每个线程会有一份tmp拷贝后,配合打印输出,就不难分析出原因。


问题解决


按照《Effective Java》一书中创建单例的推荐,可使用如下两种解决方法


双重锁检查机制

需要配合volatile关键字使用,并且需要JDK版本在1.5以上,核心代码如下

static class Singleton {
  private static volatile Singleton cache = null;
  private static Object mutexObj = new Object();
  private Singleton() {
  }
  public static Singleton getInstance() {
    Singleton tmp = cache;
      if (tmp == null) {
      tmp = cache;
      synchronized (mutexObj) {
        if (tmp == null) {                    
          tmp = new Singleton();
          cache = tmp;  
        }               
      }
    }
    return tmp;
  }
}

进行如下测试(添加打印语句方便分析):

public class Test {
  public static void main(String[] args) {  
    for (int i = 0; i < 3; i++) {
      Thread thread = new Thread(new Runnable() {
        public void run() {
          System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString());
        }
      });
      thread.setName("Thread" + i);
      thread.start();
    }
  }
  static class Singleton {
    private static volatile Singleton cache = null;
    private static Object mutexObj = new Object();
    private Singleton() {
    }
    public static Singleton getInstance() {
      Singleton tmp = cache;
          if (tmp == null) {
            System.out.println(Thread.currentThread().getName() + " in outer if block");
              synchronized (mutexObj) {
                System.out.println(Thread.currentThread().getName() + " in synchronized block");
                tmp = cache;
                  if (tmp == null) {   
                    System.out.println(Thread.currentThread().getName() + " in inner if block");
                    tmp = new Singleton();
                    try {
                      Thread.sleep(1);
                    } catch (InterruptedException e) {
                      e.printStackTrace();
                    }
                      cache = tmp;  
                  } 
                  System.out.println(Thread.currentThread().getName() + " out inner if block");
              }
              System.out.println(Thread.currentThread().getName() + " out synchronized block");
          }
          System.out.println(Thread.currentThread().getName() + " out outer if block");
          return tmp;
    }
  }
}

输出结果如下:

Thread0 in outer if block

Thread0 in synchronized block

Thread0 in inner if block

Thread2 in outer if block

Thread1 in outer if block

Thread0 out inner if block

Thread0 out synchronized block

Thread1 in synchronized block

Thread1 out inner if block

Thread1 out synchronized block

Thread1 out outer if block

Thread0 out outer if block

Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f

Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f

Thread2 in synchronized block

Thread2 out inner if block

Thread2 out synchronized block

Thread2 out outer if block

Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f

从结果中和线程运行步骤可以看到三个线程并发的情况下,只生成了唯一实例。


静态内部类

JDK版本限制,也不需要使用volatile关键字即可完成单例模式,核心代码如下:

static class Singleton {
  private Singleton() {
  }
  private static class InstanceHolder {
        public static Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
      return InstanceHolder.instance;  
  }
}

进行如下测试:

public class Test {
  public static void main(String[] args) {  
    for (int i = 0; i < 3; i++) {
      Thread thread = new Thread(new Runnable() {
        public void run() {
          System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString());
        }
      });
      thread.setName("Thread" + i);
      thread.start();
    }
  }
  static class Singleton {
    private Singleton() {
    }
    private static class InstanceHolder {
          public static Singleton instance = new Singleton();
      }
      public static Singleton getInstance() {
          return InstanceHolder.instance;  
      }
  }
}

运行结果如下:

Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7

Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7

Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7

该模式可保证使用时才会初始化变量,达到延迟初始化目的。


总结


单例模式在多线程环境下不太好编写,并且不容易重现异常,编写时需要谨慎,在项目中遇到问题也需要多总结和记录。



目录
相关文章
|
存储 缓存 开发框架
多线程环境下的伪共享
多线程环境下的伪共享
372 3
|
分布式计算 并行计算 安全
在Python Web开发中,Python的全局解释器锁(Global Interpreter Lock,简称GIL)是一个核心概念,它直接影响了Python程序在多线程环境下的执行效率和性能表现
【6月更文挑战第30天】Python的GIL是CPython中的全局锁,限制了多线程并行执行,尤其是在多核CPU上。GIL确保同一时间仅有一个线程执行Python字节码,导致CPU密集型任务时多线程无法充分利用多核,反而可能因上下文切换降低性能。然而,I/O密集型任务仍能受益于线程交替执行。为利用多核,开发者常选择多进程、异步IO或使用不受GIL限制的Python实现。在Web开发中,理解GIL对于优化并发性能至关重要。
368 0
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
311 1
|
设计模式 存储 安全
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
499 1
|
Java
Java中的`synchronized`关键字是一个用于并发控制的关键字,它提供了一种简单的加锁机制来确保多线程环境下的数据一致性。
【6月更文挑战第24天】Java的`synchronized`关键字确保多线程数据一致性,通过锁定代码块或方法防止并发冲突。同步方法整个方法体为临界区,同步代码块则锁定特定对象。示例展示了如何在`Counter`类中使用`synchronized`保证原子操作和可见性,同时指出过度使用可能影响性能。
190 4
|
Java Linux Shell
Linux环境下,让Jar项目多线程部署成为可能
Linux环境下,让Jar项目多线程部署成为可能
364 1
|
安全 Java C#
Spring创建的单例对象,存在线程安全问题吗?
Spring框架提供了多种Bean作用域,包括单例(Singleton)、原型(Prototype)、请求(Request)、会话(Session)、全局会话(GlobalSession)等。单例是默认作用域,保证每个Spring容器中只有一个Bean实例;原型作用域则每次请求都会创建一个新的Bean实例;请求和会话作用域分别与HTTP请求和会话绑定,在Web应用中有效。 单例Bean在多线程环境中可能面临线程安全问题,Spring容器虽然确保Bean的创建过程是线程安全的,但Bean的使用安全性需开发者自行保证。保持Bean无状态是最简单的线程安全策略;
556 0
|
缓存 Java 容器
多线程环境中的虚假共享是什么?
【8月更文挑战第21天】
264 0
|
Cloud Native Java 调度
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
197 0

热门文章

最新文章