设计模式之单例模式实现
单例模式是确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免多地同时修改状态。
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
Java 编写单例模式有七种写法
第一种 懒汉式单例
package com.xiaokai.constructor.singleton;
/**
* Created by Administrator on 2016/8/4.
*/
public class Singleton1 {
//私有静态实例(全局共享)
private static Singleton1 instance;
//私有构造器(外部不能实例化)
private Singleton1() {
}
//获取单例
public static Singleton1 getInstance() {
//如果没有实例化,先实例化返回
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
}
单元测试类
package com.xiaokai.constructor.singleton;
import org.junit.Assert;
import org.junit.Test;
/**
* Created by Administrator on 2016/8/4.
*/
public class Singleton1Test {
@Test
public void getInstance() throws Exception {
Singleton1 singleton1 = Singleton1.getInstance();
Singleton1 singleton11 = Singleton1.getInstance();
Assert.assertSame(singleton1, singleton1);
}
}
说明:此懒汉式单例在多线程不能正常工作(非线程安全),并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全。
1、在getInstance方法上加同步
//在获取单例方法上加锁
public static synchronized Singleton1 getInstance() {
if (single == null) {
single = new Singleton1();
}
return single;
}
这种写法虽然能够在多线程中很好的工作,但是,他的效率很低,因为单例已经存在的情况下是不需要同步的。
2、双重检查锁定
//加锁前先判单例是否已经存在,只有在不存的时候加锁实例化出来一个单例
public static Singleton1 getInstance() {
if (singleton == null) {
//注意是类级锁
synchronized (Singleton1.class) {
if (singleton == null) {
singleton = new Singleton1();
}
}
}
return singleton;
}
注意:双重检查锁定在JDK1.5之后,才能够正常达到单例效果。
3、使用静态内部类实现单例模式(推荐)
public class Singleton {
//定义私有静态内部类
private static class SingletonHolder {
//加载时实例化单例,static final类型
private static final Singleton INSTANCE = new Singleton();
}
//私有构造器
private Singleton (){}
//通过调用getInstance实例化静态内部类,返回静态内部类加载时实例化的单例
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
内部类方式既实现了线程安全,又避免了同步带来的性能影响。这种方式利用了classloder的机制来保证初始化instance时只有一个线程,这种方式情况下,Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显式通过调用getInstance方法时,才会显式装载SingletonHolder类,从而实例化instance。如果实例化instance很消耗资源,让他延迟加载是很有帮助的。
第二种 饿汉式单例
package com.xiaokai.constructor.singleton;
/**
* Created by Administrator on 2016/8/4.
*/
public class Singleton2 {
private Singleton2() {
}
//在类初始化时,实例化单例
private static final Singleton2 instance = new Singleton2();
//静态工厂方法
public static Singleton2 getInstance() {
return instance;
}
}
单元测试类
package com.xiaokai.constructor.singleton;
import org.junit.Assert;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created by Administrator on 2016/8/4.
*/
public class Singleton2Test {
@Test
public void getInstance() throws Exception {
Singleton2 singleton1 = Singleton2.getInstance();
Singleton2 singleton11 = Singleton2.getInstance();
Assert.assertSame(singleton1, singleton1);
}
}
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
第三种 枚举式单例(强烈推荐)
package com.xiaokai.constructor.singleton;
/**
* Created by Administrator on 2016/8/4.
*/
public enum Singleton3 {
INSTANCE;
public void whateverMethod() {
}
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
有两个问题需要注意:
1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类 装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
解决第一个问题的方法是重写Object的getClass方法。
private static Class getClass(String classname)
throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//先判空,再加载单例
if (classLoader == null)
classLoader = Singleton3.class.getClassLoader();
return (classLoader.loadClass(classname));
}
解决第二个问题的方法是实现序列化接口后重写readResolve方法。
public class Singleton implements java.io.Serializable {
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
private Object readResolve() {
return INSTANCE;
}
}
总结
三类:懒汉(懒汉又有双重校验锁,静态内部类),饿汉,枚举。
懒汉:需要加锁才能实现多线程同步,但是效率会降低。优点是延时加载。
双重校验锁:麻烦,在当前Java内存模型中不一定都管用,某些平台和编译器甚至是错误的,在JDK1.5之后,才能够正常达到单例效果。。
静态内部类:延迟加载,减少内存开销。因为用到的时候才加载,避免了静态field在单例类加载时即进入到堆内存的permanent代而永远得不到回收的缺点(大多数垃圾回收算法是这样)。
饿汉:因为加载类的时候就创建实例,所以线程安全(多个ClassLoader存在时例外)。缺点是不能延时加载。
枚举:很好,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。但是失去了类的一些特性,没有延迟加载。