单例模式的应用场景
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,构造方法私有化、并提供一个全局的访问点。单例模式是创建型模式。在 J2EE 标准中,ServletContext、ServletContextConfig 等;在 Spring 框架应用中ApplicationContext;数据库的连接池也都是单例形式。
饿汉式单例
//第一种写法
public class HungrySingleton {
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance() { return HUNGRY_SINGLETON; }
}
//第二种写法
public class HungryStaticSingleton {
private static final HungryStaticSingleton HUNGRY_SINGLETON;
static {
HUNGRY_SINGLETON = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance() { return HUNGRY_SINGLETON; }
}
优点:执行效率高,性能高,没有任何锁
缺点:某些情况下,可能会造成内存的浪费
懒汉式单例
public class LazySingleton {
private static LazySingleton LAZY_SINGLETON;
private LazySingleton(){}
public static LazySingleton getInstance() {
if (LAZY_SINGLETON == null) {
LAZY_SINGLETON = new LazySingleton();
}
return LAZY_SINGLETON;
}
}
优点:需要用时初始化,节省了内存浪费
缺点:线程不安全
public class LazySyncSingleton {
private static LazySyncSingleton LAZY_SINGLETON;
private LazySyncSingleton(){}
public synchronized static LazySyncSingleton getInstance() {
if (LAZY_SINGLETON == null) {
LAZY_SINGLETON = new LazySyncSingleton();
}
return LAZY_SINGLETON;
}
}
优点:需要用时初始化,节省了内存浪费,线程安全
缺点:性能低
双重检查锁单例
public class LazyDoubleCheckSyncSingleton {
private volatile static LazyDoubleCheckSyncSingleton LAZY_SINGLETON;
private LazyDoubleCheckSyncSingleton(){}
public synchronized static LazyDoubleCheckSyncSingleton getInstance() {
//检查是否要阻塞
if (LAZY_SINGLETON == null) {
synchronized (LazyDoubleCheckSyncSingleton.class) {
//检查是否要重新创建实例
if (LAZY_SINGLETON == null) {
LAZY_SINGLETON = new LazyDoubleCheckSyncSingleton();
}
}
}
return LAZY_SINGLETON;
}
}
优点:性能高,线程安全
缺点:可读性有些影响
静态内部类
public class LazyStaticInnerSingleton {
private LazyStaticInnerSingleton(){}
public static LazyStaticInnerSingleton getInstance() {
return LazyHolder.LAZY_STATIC_INNER_SINGLETON;
}
public static class LazyHolder{
public static final LazyStaticInnerSingleton LAZY_STATIC_INNER_SINGLETON = new LazyStaticInnerSingleton();
}
}
咋一看以为这是饿汉,其实还是懒汉。区别在于内部类加载方式 LazyStaticInnerSingleton.class LazyStaticInnerSingleton$LazyHolder.class
优点:写法优雅,利用了Java本身的语法特点,性能高,避免了内存浪费
缺点:能够被反射破坏
破坏单例
public class ReflectTest {
public static void main(String[] args) {
try {
Class<?> clazz = LazyStaticInnerSingleton.class;
Constructor<?> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
Object o1 = c.newInstance();
Object o2 = c.newInstance();
System.out.println(o1);
System.out.println(o2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//打印结果
spring.note.designpattern.singlepattern.lazy.LazyStaticInnerSingleton@1540e19d
spring.note.designpattern.singlepattern.lazy.LazyStaticInnerSingleton@677327b6
从打印结果来看确实创建出了两个对象,并且跳过了私有构造方法
解决办法
private LazyStaticInnerSingleton(){
throw new RuntimeException("不允许非法创建对象!");
}
//打印结果
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at spring.note.designpattern.singlepattern.lazy.ReflectTest.main(ReflectTest.java:15)
Caused by: java.lang.RuntimeException: 不允许非法创建对象!
at spring.note.designpattern.singlepattern.lazy.LazyStaticInnerSingleton.<init>(LazyStaticInnerSingleton.java:10)
... 5 more
在私有构造方法直接抛出异常。反射创建对象时就抛出了异常
注册时单例
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
尝试用反射进行破坏
public class EnumSingletonTest {
public static void main(String[] args) {
try {
Class clazz = EnumSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
System.out.println(constructor);
constructor.setAccessible(true);
constructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//控制台结果
private spring.note.designpattern.singlepattern.register.EnumSingleton(java.lang.String,int)
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at spring.note.designpattern.singlepattern.register.EnumSingletonTest.main(EnumSingletonTest.java:17)
提示无法被反射创建enmu对象,跟着错误提示看看jdk源码
@CallerSensitive
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;
}
ThreadLocal单例
public class ThreadLocalSingleton {
public static final ThreadLocal<ThreadLocalSingleton> instance = new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton (){}
public static ThreadLocalSingleton getInstance() {
return instance.get();
}
}
ThreadLocal 是线程安全的单例,每个线程之间获取的对象地址都会不同。来一段代码测试下
public class ExectorThread implements Runnable{
public void run() {
ThreadLocalSingleton instance = ThreadLocalSingleton.getInstance();
System.out.println("线程名称:" + Thread.currentThread().getName() + "," + instance);
}
}
public class ThreadLocalSingletonTest {
public static void main(String[] args) {
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
Thread thread1 = new Thread(new ExectorThread());
Thread thread2 = new Thread(new ExectorThread());
thread1.start();
thread2.start();
System.out.println("end");
}
}
//输出结果:
spring.note.designpattern.singlepattern.threadlocal.ThreadLocalSingleton@1540e19d
spring.note.designpattern.singlepattern.threadlocal.ThreadLocalSingleton@1540e19d
end
线程名称:Thread-0,spring.note.designpattern.singlepattern.threadlocal.ThreadLocalSingleton@5d84cea9
线程名称:Thread-1,spring.note.designpattern.singlepattern.threadlocal.ThreadLocalSingleton@570ead58
可以看到主线程中两次获取的一致。但是几个线程之间获取的对象都不一样。ThreadLocal特性是保证当前线程内的对象为单例。所以该项结果也是很正常的。