码迷,mamicode.com
首页 > 其他好文 > 详细

03单例模式

时间:2021-06-30 18:43:54      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:rgba   let   rgb   get   singleton   bean   没有   inpu   标识   

一、单例模式定义

单例模式是指确保一个类在任何情况下都绝对只能有一个实例,并提供一个全局访问点。减少内存开销,避免对资源的多重占用。

二、饿汉式单例

1.代码示例

 1 //标准写法
 2 public class HungrySingleton {
 3 
 4     private  HungrySingleton(){}
 5 
 6     private static final HungrySingleton hungrySingleton = new HungrySingleton();
 7 
 8     public static HungrySingleton getInstance(){
 9         return hungrySingleton;
10     }
11 }
12 
13 //静态代码块写法
14 public class HungryStaticsingleton {
15 
16     private HungryStaticsingleton(){}
17 
18     private static final HungryStaticsingleton hungrySingleton;
19 
20     static{
21         hungrySingleton = new HungryStaticsingleton();
22     }
23 
24     private static HungryStaticsingleton getInstance(){
25         return hungrySingleton;
26     }
27 }

 

2.饿汉式分析

饿汉式单例模式适用于单例对象较少的情况。可以保证绝对线程安全、执行效率比较高。其缺点也很明显,就是所有对象类加载的时候就实列化了。如果系统中有大批量的单例对象存在,系统初始化时就会导致大量的内存浪费

 

三、懒汉式单例

1.标准代码示例

 1 public class LazySimpleSingleton {
 2 
 3     private LazySimpleSingleton(){}
 4 
 5     private static LazySimpleSingleton lazy = null;
 6 
 7     public static LazySimpleSingleton getInstance(){
 8         if(lazy == null){
 9             lazy = new LazySimpleSingleton();
10         }
11         return lazy;
12     }
13 }

这样能解决饿汉式的内存浪费问题,但是在多线程环境下会出现线程安全问题,于是便再加以改进,将getInstance()方法加上synchronized同步锁

 

2.解决线程安全问题

 1 public class LazySimpleSingleton {
 2 
 3     private LazySimpleSingleton(){}
 4 
 5     private static LazySimpleSingleton lazy = null;
 6 
 7     public synchronized static LazySimpleSingleton getInstance(){
 8         if(lazy == null){
 9             lazy = new LazySimpleSingleton();
10         }
11         return lazy;
12     }
13 }

此方式解决了线程安全问题,但在线程数量比较多的情况下,若CPU分配压力上升,则会导致大批线程阻塞,从而导致程序性能大幅下降,有没有更好的方式呢?于是便有了下面双重检查锁的方式

 

3.双重检查锁单例模式

 1 public class LazyDoubleCheckSingleton {
 2 
 3     private LazyDoubleCheckSingleton(){}
 4 
 5     private volatile static LazyDoubleCheckSingleton lazy = null;
 6 
 7     public static LazyDoubleCheckSingleton getInstance(){
 8         if(lazy == null){
 9             synchronized (LazyDoubleCheckSingleton.class){
10                 if(lazy == null){
11                     lazy = new LazyDoubleCheckSingleton();
12                 }
13             }
14         }
15         return lazy;
16     }
17 }

用到synchronized关键字总归是要加上锁,对程序性能有一定影响,有没有更好的方案?请看下面方法

 

4.静态内部类方式

 1 public class LazyInnerClassSingleton {
 2 
 3     private LazyInnerClassSingleton(){}
 4 
 5     private static class LazyHolder{
 6         private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
 7     }
 8 
 9     public static final LazyInnerClassSingleton getInstance(){
10         return LazyHolder.LAZY;
11     }
12 }

这种方式兼顾了饿汉式的内存浪费问题和synchronized的性能问题。内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题。这样真的能保证单例吗?

 

5.反射破坏单例

 1 public class LazyInnerClassSingletonTest {
 2 
 3     public static void main(String[] args) {
 4 
 5         try {
 6             Class<?> clazz = LazyInnerClassSingleton.class;
 7 
 8             //通过反射获取私有的构造方法
 9             Constructor c = clazz.getDeclaredConstructor(null);
10             //强制访问
11             c.setAccessible(true);
12 
13             //暴力初始化
14             Object o1 = c.newInstance();
15 
16             //调用了两次构造方法,相当于"new"了两次,范了原则性错误
17             Object o2 = c.newInstance();
18 
19             System.out.println(o1==o2);
20             
21         }catch (Exception e){
22             e.printStackTrace();
23         }
24     }
25 }

打印结果为false,创建了两个不同的实例,解决方法:我们可以在构造方法中加一些限制,代码如下:

 1 public class LazyInnerClassSingleton {
 2 
 3     private LazyInnerClassSingleton(){
 4         if(LazyHolder.LAZY!=null){
 5             throw new RuntimeException("不允许创建多个实例!");
 6         }
 7     }
 8 
 9     private static class LazyHolder{
10         private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
11     }
12 
13     public static final LazyInnerClassSingleton getInstance(){
14         return LazyHolder.LAZY;
15     }
16 }

虽然这看似完美了,但是还是有可能被破坏!

 

6.序列化破坏单例

一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例。代码示例:

 1 public class SerializableSingleton implements Serializable {
 2 
 3     private final static SerializableSingleton INSTANCE = new SerializableSingleton();
 4 
 5     private SerializableSingleton(){}
 6 
 7     public static SerializableSingleton getInstance(){
 8         return INSTANCE;
 9     }
10 }
11 
12 public class SerializableSingletonTest {
13 
14     public static void main(String[] args) {
15         SerializableSingleton s1= null;
16         SerializableSingleton s2 = SerializableSingleton.getInstance();
17 
18         FileOutputStream fos = null;
19         try {
20             fos = new FileOutputStream("SerializableSingleton.obj");
21             ObjectOutputStream oos = new ObjectOutputStream(fos);
22             oos.writeObject(s2);
23             oos.flush();
24             oos.close();
25 
26             FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
27             ObjectInputStream ois = new ObjectInputStream(fis);
28             s1 = (SerializableSingleton)ois.readObject();
29             ois.close();
30 
31             System.out.println(s1);
32             System.out.println(s2);
33             System.out.println(s1==s2);
34         }catch (Exception e){
35             e.printStackTrace();
36         }
37     }
38 }

运行结果为false,代表反序列化后的对象和手动创建的对象是不一致的,实例化了两次,违背了单例设计的初衷。解决方案代码如下:

 1 public class SerializableSingleton implements Serializable {
 2 
 3     private final static SerializableSingleton INSTANCE = new SerializableSingleton();
 4 
 5     private SerializableSingleton(){}
 6 
 7     public static SerializableSingleton getInstance(){
 8         return INSTANCE;
 9     }
10 
11     private Object readResolve(){
12         return INSTANCE;
13     }
14 }

增加了readResolve()方法。

原理:jdk源码ObjectInputStream类的readObject()方法,依次往下看,类实际上被实例化了两次,只不过如果类中有readResolve方法,就返回这个方法的返回值,而没有返回新创建的对象。

 

四、注册式单例模式

1.注册式单例模式又称为登记式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。注册式单例模式有两种:一种为枚举式单例模式,另一种为容器式单例模式

2.枚举式单例模式

 1 public enum EnumSingleton {
 2     INSTANCE;
 3 
 4     private Object data;
 5 
 6     public Object getData(){
 7         return data;
 8     }
 9 
10     public void setData(Object data){
11         this.data = data;
12     }
13 
14     public static EnumSingleton getInstance(){
15         return INSTANCE;
16     }
17 }
18 
19 public class EnumSingletonTest {
20 
21     public static void main(String[] args) {
22         try {
23             EnumSingleton instance1 = null;
24 
25             EnumSingleton instance2 = EnumSingleton.getInstance();
26             instance2.setData(new Object());
27             FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
28             ObjectOutputStream oos = new ObjectOutputStream(fos);
29             oos.writeObject(instance2);
30             oos.flush();
31             oos.close();
32 
33             FileInputStream fis = new FileInputStream("EnumSingleton.obj");
34             ObjectInputStream ois = new ObjectInputStream(fis);
35             instance1 = (EnumSingleton)ois.readObject();
36             ois.close();
37 
38             System.out.println(instance1.getData());
39             System.out.println(instance2.getData());
40             System.out.println(instance1.getData()==instance2.getData());
41         }catch (Exception e){
42             e.printStackTrace();
43         }
44     }
45 }

运行结果为true,反编译(Jad)后发现,枚举单例模式在静态代码块中就给INSTANCE进行了赋值,是饿汉式单例模式的体现;枚举类型通过类名和类对象类找到一个唯一的枚举对象,因此,枚举对象不可能被类加载器加载多次;反射也不能破坏枚举单例模式,会报找不到无参的构造方法,不能用反射创建枚举类型。

不适合大量创建单例对象的场景

 

3.容器式单例

 1 public class ContainerSingleton {
 2 
 3     private ContainerSingleton(){}
 4 
 5     private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
 6 
 7     public static Object getBean(String className){
 8         synchronized (ioc){
 9             if(!ioc.containsKey(className)){
10                 Object obj = null;
11                 try {
12                     obj = Class.forName(className).newInstance();
13                     ioc.put(className, obj);
14                 }catch (Exception e){
15                     e.printStackTrace();
16                 }
17                 return obj;
18             }else {
19                 return ioc.get(className);
20             }
21         }
22     }
23 }

容器式单例适用于需要大量创建单例对象的场景,便于管理,但其是非线程安全的。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

03单例模式

标签:rgba   let   rgb   get   singleton   bean   没有   inpu   标识   

原文地址:https://www.cnblogs.com/it-szp/p/14953145.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!