单例模式
二、分类所谓的单例模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类对象只提供一个获取其对象实例的方法(一般都是静态方法)
单例模式大致可以分成饿汉式和懒汉式,饿汉和懒汉的区别在于创建对象的时机。
当然,要是按线程安不安全懒汉式还能分成好多种
比如双检锁模式、静态内部类模式、枚举模式等
下面我们会具体说说各种模式
饿汉式就是在调用方法获取对象之前就已经创建好了对象,不管你用不用,反正我先创建了,所以叫饿汉式
代码如下:
私有化构造器方式的饿汉式:
私有化构造器,然后直接通过字面量的方式给对象赋值
class Singleton { // 1. 构造器私有化,外部不能 new private Singleton () { } // 2. 本类内部创建对象实例 private final static Singleton instance = new Singleton() ; // 3. 对外提供一个公有的静态方法,返回对象实例 public static Singleton getInstance () { return instance ; } }
静态代码块方式的饿汉式:
因为静态代码块会随着类的加载而加载,而且只会执行一次,所以也可以用来在调用方法前给对象赋值
class Singleton { // 1. 构造器私有化,外部不能 new private Singleton () { } // 2. 本类内部创建静态变量 private static Singleton instance ; // 3. 在静态代码块中创建单例对象 static { instance = new Singleton() ; } // 3. 对外提供一个公有的静态方法,返回对象实例 public static Singleton getInstance () { return instance ; } }
饿汉式的优缺点
优点:
写法比较简单,在类加载的时候就完成了实例化,避免了线程同步的问题
缺点:
四、懒汉式在类加载的时候就完成实例化,并没有实现懒加载(Lazy Loading)的效果。如果从始至终都没有使用过这个对象实例,那么就白白造成了内存的浪费,如果这种类过多甚至会造成溢出
饿汉式就是在调用方法获取对象之前不进行实例化,当调用方法获取对象时才会去创建对象,敌不动我不懂所以叫懒汉式
懒汉式分类太多了,慢慢写吧,先看看经典的懒汉式写法
4.1经典懒汉式(线程不安全)代码如下:
class Singleton { private static Singleton instance ; public Singleton () { } // 提供一个静态的公有方法,当调用该方法时,才去创建 instance public static Singleton getInstance () { if ( instance == null ) { instance = new Singleton() ; } return instance ; } }
然后来说说优缺点吧
经典的懒汉式实现了懒加载,但是只能在单线程的情况下使用
如果在多线程下,一个线程进入了if,还没来得及向下执行,另一个线程也通过了if,这个时候就会产生多个实例的对象,所以在多线程的环境下不可以使用这种方法。
代码如下:
class Singleton { private static Singleton instance ; public Singleton () { } // 提供一个静态的公有方法,当调用该方法时,才去创建 instance // 加入 synchronized 同步锁,解决线程安全问题 public static synchronized Singleton getInstance () { if ( instance == null ) { instance = new Singleton() ; } return instance ; } }
优缺点:
优点就是解决了4.1的线程不安全问题,
但是这种方式效率太低了,整个同步方法,每个线程在获取对象实例的时候都要进行同步,而其实这个对象只需要进行一次实例化,后面想获得直接返回就好了,根本不需要同步。
代码如下:
public class Singleton { private static volatile Singleton singleton ; public Singleton () { } public static Singleton getInstance () { // 1.先判断是否已经实例化,如果实例化直接返回 if ( singleton == null ) { // 2.如果没有实例化且有多个线程进去,需要抢锁 synchronized ( Singleton . class ) { // 再次判断是否被实例化,如果有多个线程从1进来,就可以解决被多次实例化的问题 if ( singleton == null ) { singleton = new Singleton() ; } } } return singleton ; } }
双检锁方式的优点:
- 解决了线程安全的问题
- 实现了懒加载
- 效率相比于同步方法的方式要高一点
因为Java内部类的特性,在外部类加载的时候并不会立即加载其内部类,而是当调用其内部类的时候才会发生加载,而静态内部类也只会被实例化一次,所以也可以用来做单例模式。
代码如下:
public class Singleton { private Singleton () { } // 静态内部类,该类中有一个静态属性 Singleton private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton() ; } // 提供一个静态的公有方法,直接返回 SingletonInstance.INSTANCE public static Singleton getInstance () { return SingletonInstance.INSTANCE; } }
静态内部类方式的缺点:
4.5枚举静态内部类的方式有一个致命的缺点,没有办法传参,所以如果不需要传递参数的话,这种方式是比较推荐使用的
在Java里枚举类实例天生就是线程安全的,而且任何情况下他都是单例的,可以直接作为单例模式来使用。
而且Effective Java中也推荐这种方式,而且枚举类自动支持序列化
代码如下:
public enum Singleton { // 属性 INSTANCE ; public void sayHello () { // 行为代码 } }
关于枚举是否是懒加载的,得到的结论是是懒加载的,但是我并不知道如何去证明。
五、破坏单例模式与预防破坏单例模式的方式可以分为两种:反射和反序列化
首先讲一讲反射如何破坏单例模式和如何预防
除了枚举单例模式,别的方式我们都可以通过反射获取到单例的类私有的构造器,然后通过私有的构造器创建一个该类的实例出来
预防的话,可以在构造器中判断实例对象是否为null,如果不为null,则直接抛出异常
反序列化破坏单例模式和预防
如果我们的单例类实现了Serializable接口,就可以通过反序列化的方式来破坏单例
解决方案很简单啊,别实现Serializable接口就好了嘛~~
如果你实在头铁,非要去实现Serializable接口,那么你就需要重写Serializable中的反序列化方法readResolve(),在该方法中直接返回单例对象即可。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)