Java单例设计模式线程安全问题分析

  今天突然看到Java的设计模式,第一个出现的当然就是单例模式,这也是最为熟知的设计模式。单例设计模式有多种方法,其中有一种是懒汉模式,在这种模式下,调用方需要获取对象实例时才会创建对象并返回,而这种模式存在线程不安全的问题,付之实践测试一番。

代码如下:
 1 package com.xjl;
 2 
 3 /**
 4  * 测试单例模式
 5  */
 6 public class TestSingleton {
 7     public static void main(String[] args) {
 8         for (int i = 0; i < 10; i++) {
 9             Thread thread = new Thread(new Runnable() {
10                 @Override
11                 public void run() {
12                     LazyMode instance = LazyMode.getInstance();
13                     System.out.println("当前线程"+Thread.currentThread().getName()+"实例的hashCode:" + instance);
14                 }
15             });
16             //System.out.println(thread.getName());
17             thread.start();
18         }
19     }
20 }
21 
22 class LazyMode{
23     private LazyMode(){
24         System.out.println("Lazy Mode initial...");
25     }
26 
27     private static LazyMode instance = null;
28 
29     public static LazyMode getInstance(){
30         if(instance == null){
31             instance = new LazyMode();
32         }
33         return instance;
34     }
35 }


输出如下:===============================================      

  Lazy Mode initial...
  Lazy Mode initial...
  当前线程Thread-1实例的hashCode:com.xjl.LazyMode@40f53ce9
  当前线程Thread-7实例的hashCode:com.xjl.LazyMode@60cb300b
  Lazy Mode initial...
  当前线程Thread-4实例的hashCode:com.xjl.LazyMode@175e35fd
  当前线程Thread-9实例的hashCode:com.xjl.LazyMode@175e35fd
  Lazy Mode initial...
  当前线程Thread-0实例的hashCode:com.xjl.LazyMode@7e7e475e
  当前线程Thread-3实例的hashCode:com.xjl.LazyMode@7e7e475e
  当前线程Thread-5实例的hashCode:com.xjl.LazyMode@60cb300b
  当前线程Thread-8实例的hashCode:com.xjl.LazyMode@60cb300b
  当前线程Thread-2实例的hashCode:com.xjl.LazyMode@7e7e475e
  当前线程Thread-6实例的hashCode:com.xjl.LazyMode@7e7e475e

 

当然了,并不是每次输出都会是上面这样,创建多个实例。我尝试运行多次才出现上面的输出。究其原因:

假设有多个线程1,线程2,线程n都需要使用这个单例对象。而恰巧,线程1在判断完instance == null后突然交换了CPU的使用权,变为线程7执行,由于instance仍然为null,那么线程7中就会创建这个LazyMode的单例对象。之后线程1拿回CPU的使用权,而正好线程1之前暂停的位置就是判断instance是否为null之后,创建对象之前。这样线程1又会创建一个新的LazyMode对象。造成了线程不安全的问题。

 

为避免线程的不安全,我们可以使用饿汉式的设计模式:

 1 class HungryMode{
 2     private HungryMode(){
 3         System.out.println("Hungry Mode initial...");
 4     }
 5 
 6     private static HungryMode instance = new HungryMode();
 7 
 8     public static HungryMode getInstance(){
 9         return instance;
10     }
11 }

 

将instance声明为static,在类被加载、初始化时就已经创建出来,而且只会创建一次。这样多个线程同时获取实例对象时,获取到的都是同一个实例。但是在未使用对象实例时就已经创建完成,造成内存的浪费,称之为饿汉式。

 

或者使用改进后的懒汉模式:

 1 class LazyModeImproved{
 2 
 3     private static volatile LazyModeImproved instance = null;
 4 
 5     private LazyModeImproved(){}
 6 
 7     public static LazyModeImproved getInstance(){
 8         if(null == instance){
 9             synchronized (LazyModeImproved.class){
10                 if(null == instance){
11                     instance = new LazyModeImproved();
12                 }
13             }
14         }
15         return instance;
16     }
17 }

 

 

 

 

posted @ 2021-08-04 15:42  非常非常滴亮  阅读(214)  评论(0)    收藏  举报