单例模式
Posted wxc-xiaohuang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式相关的知识,希望对你有一定的参考价值。
单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。
<br/>
饿汉式: 类加载的时候就实例化对象了,线程安全,但是浪费内存空间。如果该实例从始至终都没被使用过,则会造成内存浪费。
public class Singleton001 {
private static Singleton001 instance = new Singleton001();
private Singleton001() {}
public static Singleton001 getInstance() {
return instance;
}
}
懒汉式 :需要使用的时候才去加载,节约了内存空间,浪费了时间,线程不安全的。
public class LazySingleton001 {
private static LazySingleton001 instance;
private LazySingleton001() {}
public static LazySingleton001 getInstance() {
if (instance == null) {
instance = new LazySingleton001();
}
return instance;
}
}
方法加锁: 在getInstance() 上加了 synchronized 关键字,方法同步,线程安全了,但是效率太低。 每个线程如果想获得类的实例,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。
public class LazySingleton002 {
private static LazySingleton002 instance;
private LazySingleton002() {}
public static synchronized LazySingleton002 getInstance(){
if(instance == null){
instance = new LazySingleton002();
}
return instance;
}
}
减小锁粒度, 把 synchronized 关键字放在 if块里面,但是这样有可能产生多实例。假如一个线程进入了if (singleton == null)判断语句块,还没来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
public class LazySingleton003 {
private static LazySingleton003 instance;
private LazySingleton003() {}
public static LazySingleton003 getInstance() {
if (instance == null) {
synchronized(LazySingleton003.class) {
instance = new LazySingleton003();
}
}
return instance;
}
}
双重校验:进行了两次null检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断是否为 null,直接return实例化对象。同时,对singleton对象使用volatile关键字进行限制,保证其对所有线程的可见性,并且禁止对其进行指令重排序优化。
public class DoubleCheckSingleton004 {
private static volatile DoubleCheckSingleton004 instance;
private DoubleCheckSingleton004() {}
public static DoubleCheckSingleton004 getInstance() {
if (instance == null) {
synchronized (DoubleCheckSingleton004.class) {
if (instance == null) {
instance = new DoubleCheckSingleton004();
}
}
}
return instance;
}
}
静态内部类:采用了类装载的机制来保证初始化实例时只有一个线程。在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
public class StaticInnerClassSinleton005 {
private StaticInnerClassSinleton005() {}
private static class SingletonInstance {
private static final StaticInnerClassSinleton005 INSTANCE = new StaticInnerClassSinleton005();
}
public static StaticInnerClassSinleton005 getInstance() {
return SingletonInstance.INSTANCE;
}
}
其实静态内部类的单例模式优缺点,要是我们强行通过Java的反射机制来攻击静态内部类的单例模式,代码如下:
public class ReflectAccackTest {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class<?> classType = StaticInnerClassSinleton005.class;
Constructor<?> cons;
cons = classType.getDeclaredConstructor(null);
cons.setAccessible(true);
StaticInnerClassSinleton005 sin1 = (StaticInnerClassSinleton005)cons.newInstance();
StaticInnerClassSinleton005 sin2 = StaticInnerClassSinleton005.getInstance();
System.out.println(sin1 == sin2);
}
}
运行结果:false
我们发现,通过反射获取构造函数,然后调用setAccessible(true)就可以调用私有的构造函数,所有sin1和sin12是两个不同的对象。
如果要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常,代码如下:
单例类:
public class StaticInnerClassSinleton006 {
private static boolean flag = false;
private StaticInnerClassSinleton006() {
synchronized (StaticInnerClassSinleton006.class) {
if (flag == false) {
flag = !flag;
} else {
throw new RuntimeException("单例模式被破坏啦!");
}
}
}
private static class SingletonInstance {
private static final StaticInnerClassSinleton006 INSTANCE = new StaticInnerClassSinleton006();
}
public static StaticInnerClassSinleton006 getInstance() {
return SingletonInstance.INSTANCE;
}
}
测试类:
public class AgainstReflectAttacksTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
try {
Class<?> classType = StaticInnerClassSinleton006.class;
Constructor<?> cons = classType.getDeclaredConstructor(null);
cons.setAccessible(true); //
StaticInnerClassSinleton006 s1 = (StaticInnerClassSinleton006)cons.newInstance();
StaticInnerClassSinleton006 s2 = StaticInnerClassSinleton006.getInstance();
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
你看,成功阻止了单例模式被破坏。
枚举,JDK1.5 之后才加入 enum 特性,这种方式是 Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
```
public enum EnumSingleton {
INSTANCE;
public void test(){
System.out.println("it is a test");
}
}
以上是关于单例模式的主要内容,如果未能解决你的问题,请参考以下文章