单例模式,是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例, 并且该类只提供一个取得其对象的静态方法。
其实,我个人说到单例模式的第一感觉就是使用这个线程不安全的方式去实现,毕竟简单嘛。但是,这种方式有一个严重的问题就是 线程不安全,当线程A进入到if语句内,还未执行new语句的时候,另外一个线程B判断此时的instance为null,也进入了if语句。 此时就会产生多个实例。
结论:虽然达到了懒加载的效果,但是只能单线程使用(开发中一般都是多线程的,不能埋下隐患),所以不推荐使用。 //方法3:懒汉模式--线程不安全 class Singleton { //1、定义类对象 private static Singleton instance; private Singleton() { } //2、懒加载类对象实例 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }在上一个方法的基础上,在实例获取getInstance()方法中添加synchronized关键字,同步方法。
解决了线程安全问题效率过低(因此不推荐使用):由于获取该类实例时都会进行同步,但是实例化却只需要一次,因此, 后续获取实例都会因为同步导致时间浪费,效率低。 //方法4:懒汉模式--同步方法实现--线程安全 class Singleton { private static Singleton instance; private Singleton() { } //使用同步方法 public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }同上一个
//方法5:懒汉模式--同步代码块--线程安全 class Singleton { private static Singleton instance; private Singleton() { } //使用同步代码块 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) {//静态方法使用Class作为同步锁 instance = new Singleton(); } } return instance; } }在看到这个方法的时候,由于看漏了volatile关键字,内心便产生了如下疑惑:好像也会出现不安全的问题? 这里先解释下Java的volatile关键字:
当 volatile 用于一个作用域时,Java保证如下:(适用于Java所有版本)读和写一个 volatile 变量有全局的排序。 也就是说每个线程访问一个 volatile 作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。 (但是并不保证经常读写 volatile 作用域时读和写的相对顺序,也就是说通常这并不是有用的线程构建)。 (适用于Java5及其之后的版本) volatile 的读和写创建了一个happens-before关系,类似于申请和释放一个互斥锁。 使用volatile会比使用锁更快,但是在一些情况下它不能工作。 volatile使用范围在Java5中得到了扩展,特别是双重检查锁定现在能够正确工作。
这里,我们需要知道volatile比使用锁更快(效率高)并且保证写instance总在读instance之前(线程安全)即可。
优缺点:
线程安全(进行类两次if检查且instance用volatile声明)第一次实例化后,后续访问if(instance ==null)时直接返回实例化对象,避免反复进行方法同步。兼具效率、安全、懒加载(推荐使用) //方法6:双重检查--线程安全 class Singleton { //使用volatile关键字 private static volatile Singleton instance; private Singleton() { } //双重检查机制,在解决线程安全的同时,解决了懒加载,或许这就是双赢吧 //不对,效率也有了保证(volatile),三赢 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }同样,先解释下静态内部类作用:
由于静态内部类是不暴露在外面的,所以使用静态内部类时,我们唯一提供静态内部类类加载的 地方也就是getInstance()入口了,并且静态内部类中的INSTANCE使用了final static进行修饰, 只会在第一次类加载时初始化,因此保证了线程安全。
总结:
巧妙使用了jvm中类加载以及静态变量的特性,兼具线程安全与懒加载、效率高推荐使用 //方法7:静态内部类实现 class Singleton{ private Singleton() { } private static class SingleInstance{ private final static Singleton INSTANCE = new Singleton(); } public static synchronized Singleton getInstance(){ return SingleInstance.INSTANCE; } }最后一种方法,也是《effective java》作者推荐的方法,使用了枚举类实现单例模式(太巧妙了吧)。 这里,枚举类型是线程安全的,只会装载一次,不会被破坏(其他方法可以通过反射以及反序列化,破坏单例模式,由于这一块并不是十分熟悉, 暂时就不做说明了)。
总结:
可以避免线程同步、防止反序列化重新创建新的对象推荐使用 //方法8:枚举类实现 //使用枚举 enum Singleton { INSTANCE;//属性 public void sayOK() { System.out.println("ok----"); } }