java单例模式及其安全发布(含饿汉式、饱汉式和错误示例)

适用场景

保证一个类仅有一个实例,并提供一个访问它的全局访问点。例如只应该有一个文件系统来保证文件的正确访问。
JDK中 java.lang.Runtime#getRuntime()就是一个典型应用:

Every Java application has a single instance of classRuntime that allows the application to interface with the environment in which the application is running. The current runtime can be obtained from thegetRuntime method.

An application cannot create its own instance of this class.

实现及安全性分析

实现一、饿汉式(线程安全)

package com.wenc.designpattern;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Singleton {

	/**
	 * 类实例变量
	 * private:保证变量不能被使用线程直接获取
	 * static:instance由JVM在类初始化阶段执行,即在类被加载后并且被线程使用之前完成。
	 * 由于JVM将在初始化期间获得一个锁,并且每个线程都至少获取一次这个锁以确保这个类已经加载,
	 * 因此在静态初始化期间,内存写入操作自动对所有线程科技那。因此无论是在构造期间还是被引用时,
	 * 静态初始化的对象都不需要显示同步。然而,这个队则仅适用于在构造时的状态,
	 * 如果对象可变,那么在读线程和写线程之间仍需要同步。
	 */
	private static Singleton instance = new Singleton();
	
	/**
	 * 访问入口
	 * @return 唯一类实例
	 */
	public static Singleton getSingleton() {
		return instance;
	}
	
	/**
	 * private:保证使用线程不能直接初始化实例,仅能通过getSingleton唯一入口获取
	 */
	private Singleton() {
		System.out.println("singleton initial~");
	}
	
	public static void main(String[] args) {
		System.out.println("main begin");
		ExecutorService exec = Executors.newFixedThreadPool(10);
		Runnable run = new Runnable() {
			public void run() {
				//打印获得的类实例信息,用于验证是否唯一
				System.out.println(Singleton.getSingleton());
			}
		} ;
		
		System.out.println("go~");
		exec.execute(run);
		exec.execute(run);
		System.out.println("done~");
	}
}
输出:
singleton initial~
main begin
go~
com.wenc.designpattern.Singleton@50f7270b
done~
com.wenc.designpattern.Singleton@50f7270b
优点: Singleton类加载时(第一个使用者线程调用getInstance时)初始化唯一类实例 利用JVM类初始化机制保证线程安全,无需显示同步;
缺点: 类加载时就占用内存空间,对于内存敏感的应用不利。
java.lang.Runtime#getRuntime()正是用“饿汉式”实现。

实现二、饱汉式(线程安全)

package com.wenc.designpattern;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class SingletonHolder {
	
	/**
	 * @author 文超
	 * 内部类,触发时才加载
	 */
	public static class Singleton {
		
		private static Singleton instance = new Singleton();
		
		private Singleton() {
			System.out.println(Thread.currentThread() + " : singleton initial~");	
		}
	}
	
	/**
	 * 外部类调用该方法时触发Singleton类加载
	 * @return 类实例
	 */
	private static Singleton getInstance() {
		return Singleton.instance;
	}
	
	public static void main(String[] args) {
		System.out.println("main begin");
		Executor exec = Executors.newFixedThreadPool(10);
		Runnable run = new Runnable() {
			public void run() {
				System.out.println(Thread.currentThread() + " : " + SingletonHolder.getInstance());
			}
		} ;
		
		System.out.println("go~");
		exec.execute(run);
		exec.execute(run);
		System.out.println("done~");
	}
}
输出:
main begin
go~
done~
Thread[pool-1-thread-1,5,main] : singleton initial~
Thread[pool-1-thread-1,5,main] : com.wenc.designpattern.SingletonHolder$Singleton@2c87f1ce
Thread[pool-1-thread-2,5,main] : com.wenc.designpattern.SingletonHolder$Singleton@2c87f1ce
与实现一的区别:singleton initial日志输出在main begin之后,说明main方法使用到singleton.instance时才初始化了内部类Singleton,比实现一更晚。

实现三、双重检查加锁DCL(线程不安全)

package com.wenc.designpattern;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class WrongSingleton {
	//可加volatile使instance可见,保证使用instance时其已被完整构造,实现线程安全
	private static WrongSingleton instance;
	
	private WrongSingleton() {
		System.out.println("WrongSingleton()");
	}
	
	public static WrongSingleton getInstance() {
		if(instance == null) {
			synchronized(WrongSingleton.class) {
				if(instance == null) {
					//下面这句包含两个方面:1、对象各域的初始化;2、发布引用,即引用赋值给instance.
					//如果无法确保发布共享引用的操作在另一个线程加载该共享引用之前执行,那么对新对象引用的写入操作
					//将于对象中各个域的写入操作重排序。造成使用线程仅看到一个部分构造的对象。
					instance = new WrongSingleton();
				}
			}
		}
		return instance;
	}
	
	public static void main(String[] args) throws InterruptedException {
		//Executor exec = new NewThreadExecutor();
		Executor exec = Executors.newFixedThreadPool(10);
		Runnable run = new Runnable() {
			public void run() {
				System.out.println(Thread.currentThread() + " : " + WrongSingleton.getInstance());
			}
		} ;
		
		System.out.println("go~");
		exec.execute(run);
		//Thread.sleep(1000);
		exec.execute(run);
		System.out.println("done~");
	}
}

输出:
go~
done~
WrongSingleton()
Thread[pool-1-thread-2,5,main] : com.wenc.designpattern.WrongSingleton@12b68fd4
Thread[pool-1-thread-1,5,main] : com.wenc.designpattern.WrongSingleton@12b68fd4
本例输入并未输出类实例部分构造的结果
改进:如果将instance变量加上volatile属性,则能保证完整构造,实现线程安全。

总结

建议采用实现二,使用时才初始化且保证线程安全,实现安全发布。



你可能感兴趣的:(设计模式)