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 }

浙公网安备 33010602011771号