单例模式
单例模式的应用场景
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并 提供一个全局访问点。单例模式是创建型模式。在 J2EE 标准中,ServletContext、 ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接 池也都是单例形式。
单例模式的常见写法
1、饿汉式单例
饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题。优点:
没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。缺点:
类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅 坑不拉屎。 Spring 中 IOC 容器 ApplicationContext 本身就是典型的饿汉式单例饿汉式单例写法:public class HungrySingleton {
private final static HungrySingleton hungrySingleton = new HungrySingleton();
// 或者:
// private final static HungrySingleton hungrySingleton ;
// static {
// hungrySingleton = new HungrySingleton();
// }
private HungrySingleton(){
}
public static HungrySingleton getHungrySingleton() {
return hungrySingleton;
}
}
2、懒汉式单例
懒汉式单例的特点是:被外部类调用的时候内部类才会加载代码:public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(){
}
public static LazySingleton getLazySingleton() {
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
线程不安全,双重检查锁 public class LazySingleton {
private volatile static LazySingleton lazySingleton = null;
private LazySingleton(){
}
public static LazySingleton getLazySingleton() {
if(lazySingleton == null){
synchronized (LazySingleton.class){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
//1.分配内存给这个对象
//2.初始化对象
//3.设置lazy指向刚分配的内存地址
//CPU内存重排序,有可能2和3颠倒,解决:加volatile关键字
}
}
}
return lazySingleton;
}
}
内部类方式单例: //这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
//完美地屏蔽了这两个缺点
public class LazyInnerClassSingleton {
//默认使用LazyInnerClassGeneral的时候,会先初始化内部类
//如果没使用的话,内部类是不加载的
private LazyInnerClassSingleton() {
}
//每一个关键字都不是多余的
//static 是为了使单例的空间共享
//保证这个方法不会被重写,重载
public static final LazyInnerClassSingleton getLazyInnerClassSingleton(){
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
静态内部类方式极端情况下单例会被破坏,如下代码 public class LazyInnerClassSingletonTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = LazyInnerClassSingleton.class;
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Object o1 = declaredConstructor.newInstance();
Object o2 = declaredConstructor.newInstance();
System.out.println(o1 == o2);//false
}
}
可以在构造函数那里判断拦截一下 private LazyInnerClassSingleton() { if(LazyHolder.LAZY != null){ throw new RuntimeException("不允许构建多个单例"); } }变为: //这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
//完美地屏蔽了这两个缺点
public class LazyInnerClassSingleton {
//默认使用LazyInnerClassGeneral的时候,会先初始化内部类
//如果没使用的话,内部类是不加载的
private LazyInnerClassSingleton() {
if(LazyHolder.LAZY != null){
throw new RuntimeException("不允许构建多个单例");
}
}
//每一个关键字都不是多余的
//static 是为了使单例的空间共享
//保证这个方法不会被重写,重载
public static final LazyInnerClassSingleton getLazyInnerClassSingleton(){
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
到此,还有更极端情况,序列化和反序列化方式也能破坏单例代码如下: public class SeriableSingleton implements Serializable{
private static final SeriableSingleton seriableSingleton= new SeriableSingleton();
private SeriableSingleton() {
}
public static SeriableSingleton getSeriableSingleton() {
return seriableSingleton;
}
private String sayHello(){
return "hello";
}
}
//测试类:
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getSeriableSingleton();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SeriableSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
解决办法:
重写readResolve变为: import java.io.Serializable;
//序列化就是说把内存中的状态通过转换成字节码的形式
//从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO)
//内存中状态给永久保存下来了
//反序列化
//讲已经持久化的字节码内容,转换为IO流
//通过IO流的读取,进而将读取的内容转换为Java对象
//在转换过程中会重新创建对象new
public class SeriableSingleton implements Serializable{
private static final SeriableSingleton seriableSingleton= new SeriableSingleton();
private SeriableSingleton() {
}
public static SeriableSingleton getSeriableSingleton() {
return seriableSingleton;
}
private Object readResolve(){
return seriableSingleton;
}
private String sayHello(){
return "hello";
}
}
3、注册式单例
注册式单例有两种写法:一种为容器缓存,一种为枚举登记
枚举式单例代码:public enum EnumSingleton implements Serializable{
INSTANCE;
public static EnumSingleton getInstance(){
return INSTANCE;
}
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
private String sayHello(){
return "hello Word";
}
}
不重写readResolve情况下 public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton s1 = null;
EnumSingleton s2 = EnumSingleton.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (EnumSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试为true用jad反编译工具可以看到枚举登记单例的代码: public final class EnumSingleton extends Enum
implements Serializable
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(com/exercise/eg/patterns/singleton/register/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public static EnumSingleton getInstance()
{
return INSTANCE;
}
public Object getData()
{
return data;
}
public void setData(Object data)
{
this.data = data;
}
private String sayHello()
{
return "hello Word";
}
public static final EnumSingleton INSTANCE;
private Object data;
private static final EnumSingleton $VALUES[];
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
}
发现枚举类型其实通过类名和 Class 对象类找到一个唯一的枚举对象。因此,枚举对 象不可能被类加载器加载多次次。那么反射是否能破坏枚举式单例呢public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = EnumSingleton.class;
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class,Integer.class);
constructor.setAccessible(true);
constructor.newInstance("Jim",666);
}
运行结果:Cannot reflectively create enum objects
看看 JDK 源码,进入 Constructor 的 newInstance()方法: public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } }
if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); }@SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
在 newInstance()方法中做了强制性的判断,如果修饰符是 Modifier.ENUM 枚举类型, 直接抛出异常。
枚举式单例也是《Effective Java》书中推荐的一种单例实现写法。在 JDK 枚举的语法特殊性,以及反射也为枚举保 驾护航,让枚举式单例成为一种比较优雅的实现注册式单例 容器缓存的写法 //Spring中的做法,就是用这种注册式单例
public class ContainerSingleton {
private static Map<String,Object> iocMap = new ConcurrentHashMap<String,Object>();
public static Object getInstance(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
synchronized (iocMap){
if(!iocMap.containsKey(className)){
Class<?> aClass = Class.forName(className);
Object o = aClass.newInstance();
iocMap.put(className, o);
return o;
}
return iocMap.get(className);
}
}
}
//测试:
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
Object object = ContainerSingleton.
getInstance("com.exercise.eg.patterns.singleton.register.Pojo");
System.out.println("end");
}
4、ThreadLocal单例
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> singletonThreadLocal =
new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
public static ThreadLocalSingleton getSingletonThreadLocal() {
return singletonThreadLocal.get();
}
private ThreadLocalSingleton() {
}
}
ThreadLocal 不能保证其 创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。