概述
创建型设计模式之单例模式
- 单例模式
- 概述
- 优缺点
- 懒汉式
- 饿汉式
- 其他实现方式
- 静态内部类实现单例
- ThreadLocal实现线程单例
- 注册式单例模式
- 枚举式单例模式
- 容器式单例模式
- 单例模式的破坏
- 反射破坏
- 序列化破坏
单例模式
概述
单例模式保证一个对象在JVM中只能有一个实例,减少内存开销,避免对资源的多重占用。单例模式属于创建型模式。
在单例模式中,单例类只能有一个实例,必须由自己创建自己的唯一实例,给所有其他对象提供这一实例。
常见单例:
懒汉式:就是需要的才会去实例化,线程不安全。
饿汉式:就是当class文件被加载的时候,初始化,天生线程安全。
比如:ServletContext、ServletContextConfig、ApplicationContext等都是单例形式
优缺点
优点:
1.在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2.避免对资源的多重占用。
缺点:
1.没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
懒汉式
懒汉式单例模式是对象要在被使用时才会初始化,能解决饿汉式单例可能带来的内存浪费问题
注意:该方式在多线程中使用存在线程安全问题。
public class Singleton {
//需要时才会被实例化
private static Singleton singleton;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
使用synchronized
关键字使方法变成线程同步方法。
这种方法在线程数量较多情况下,如果CPU压力过大,则会导致大批线程阻塞,从而导致程序性能大幅下降。
public class Singleton {
//需要时才会被实例化
private static Singleton singleton;
private Singleton() {
}
synchronized public static Singleton getSingleton() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
public static void main(String[] args) {
Singleton sl1 = Singleton.getSingleton();
Singleton sl2 = Singleton.getSingleton();
System.out.println(sl1 == sl2);
}
使用双重检查锁的的单例模式可进一步提升性能。
public class Singleton {
//需要时才会被实例化
private static Singleton singleton;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
//检查是否要重新创建实例
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
饿汉式
饿汉式单例模式在类加载的时候就立即初始化,并且创建单例对象。它绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题。
适用于单例对象较少的情况。如果系统中有大批量的单例对象存在,那系统初始化是就会导致大量的内存浪费。
优点:能保证绝对线程安全、执行效率比较高
缺点:所有对象类加载的时候就实例化
public class Singleton{
//当class文件被加载初始化
private static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getSingleton() {
return singleton;
}
}
public static void main(String[] args) {
Singleton singleton1 = Singleton.getSingleton();
Singleton singleton2 = Singleton.getSingleton();
System.out.println(singleton1 == singleton2);
}
上述是标准写法,还有另外一种写法,利用静态代码块的机制
public class Singleton{
//当class文件被加载初始化
private static Singleton singleton;
static{
singleton = new Singleton()
}
private Singleton() {}
public static Singleton getSingleton() {
return singleton;
}
}
其他实现方式
静态内部类实现单例
由于饿汉式单例模式存在内存浪费问题,懒汉式单例模式有
synchronized
性能问题。
实际上可以从类的初始化角度,采用静态内部类的方式,利用内部类一定是要在方法调用之前初始化的特点,巧妙避免线程安全问题
public class Singleton {
private Singleton() {
if (LazySingleton.INSTANCE != null) {
throw new RuntimeException("非法访问");
}
}
/**
* 在返回结果前先加载内部类
*/
private static Singleton getInstance() {
return LazySingleton.INSTANCE;
}
/**
* 默认不加载
*/
private static class LazySingleton {
private static final Singleton INSTANCE = new Singleton();
}
}
ThreadLocal实现线程单例
ThreadLocal不能保证其创建的对象是全局唯一的,但是能保证在单个线程中是唯一的,天生是线程安全的
单例模式为了线程安全会给方法加锁,以时间换空间。ThreadLocal将所有的对象全部放在ThreadLocalMap中,为每个线程都提供一个对象,实际上是以空间换时间实现线程隔离。
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =
new ThreadLocal<ThreadLocalSingleton>() {
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton() {
}
public static ThreadLocalSingleton getInstance() {
return threadLocaLInstance.get();
}
public static void main(String[] args) {
ThreadLocalSingleton threadLocalSingleton1 = ThreadLocalSingleton.getInstance();
ThreadLocalSingleton threadLocalSingleton2 = ThreadLocalSingleton.getInstance();
System.out.println(threadLocalSingleton1 == threadLocalSingleton2);
}
}
注册式单例模式
注册式单例模式又称为登记式单例模式,将每一个实例都登记到某一个地方,使用唯一的标识获取实例。
注册式单例模式有两种:枚举式单例模式
、容器式单例模式
枚举式单例模式
枚举式单例模式是比较推荐的一种单例模式实现,写法优雅
注意:与饿汉式类似,在类加载时就将所有对象初始化,不适合大量创建单例对象的场景
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
EnumSingleton instance1 = EnumSingleton.getInstance();
EnumSingleton instance2 = EnumSingleton.getInstance();
System.out.println(instance1 == instance2);
}
}
容器式单例模式
容器式单例模式适用于需要大量创建单例对象的场景,便于管理。注意:它是非线程安全的。
public class ContainerSingleton {
private ContainerSingleton() {
}
private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className) {
Object instance = null;
if (!ioc.containsKey(className)) {
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
} catch (Exception e) {
e.printStackTrace();
}
return instance;
} else {
return ioc.get(className);
}
}
public static void main(String[] args) {
Object instance1 = ContainerSingleton.getInstance("cn.ybzy.demo.ContainerSingleton");
Object instance2 = ContainerSingleton.getInstance("cn.ybzy.demo.ContainerSingleton");
System.out.println(instance1 == instance2);
}
}
单例模式的破坏
反射破坏
如果一个单例模式的私有构造不做任何处理,就可以通过反射破坏单例模式
private Singleton() {}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<Singleton> singletonClass = Singleton.class;
// 反射强制获取私有构造函数
Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor(null);
// 强制访问私有构造函数
declaredConstructor.setAccessible(true);
// 暴力调用私有构造函数进行初始化
Singleton singleton1 = declaredConstructor.newInstance();
Singleton singleton2 = declaredConstructor.newInstance();
System.out.println(singleton1 == singleton2);
}
在私有构造函数中添加限制处理,防止实例多次重复创建。
private Singleton() {
if (LazySingleton.INSTANCE != null) {
throw new RuntimeException("非法访问");
}
}
序列化破坏
序列化是将一个创建好的对象写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。由于反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就会破坏单例
public class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton() {
}
public static SeriableSingleton getInstance() {
return INSTANCE;
}
// private Object readResolve(){ return INSTANCE;}
public static void main(String[] args) {
SeriableSingleton s1 = SeriableSingleton.getInstance();
SeriableSingleton s2 = null;
FileOutputStream fos = null;
try {
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s2 = (SeriableSingleton) ois.readObject();
ois.close();
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
只需要增加一个叫
readResolve
方法返回实例就可以解决单例模式被序列化破坏的问题。
关键核心在于ObjectInputStream
中readObject(Class<?> type)
方法调用readObject0
方法中的switch (tc)
判断处理。
注意:实际上还是会实例化多次,只不过新创建的对象没有被返回而已。如果创建对象的发生频率加快,则内存分配开销也会随之增大
private Object readResolve(){ return INSTANCE;}
最后
以上就是忧心星星为你收集整理的创建型设计模式之单例模式单例模式其他实现方式的全部内容,希望文章能够帮你解决创建型设计模式之单例模式单例模式其他实现方式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复