单例模式
单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象,保证一个类仅有一个实例,并且提供一个它的全局访问点
特点
- 只有一个实例
- 必须自己创建自己的唯一实例
- 必须给所以其他对象提供这一个实例
实现方式
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
实现的基本思路
单例模式要求类能够有返回对象的一个引用(并且永远是同一个)和一个获得该实例的方法(必须是静态方法,往往使用getInstance()这个方法)
主要通过以下步骤:
(1)将该类的构造方法定义为私有方法,这样其它的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
(2)在该类种提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋值给该类保持的引用。
注意事项:单例模式在多线程的环境下必须小心使用,如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被创建了出来,从而违反了单例模式种实例唯一的原则,解决这个问题办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)
单例模式的写法
1、饿汉式(静态常量)
1 | public class singleton(){ |
优点:写法简单,就是在类加载的时候完成实例化,避免了线程同步问题。
缺点:没有达到懒加载的效果,如果从始至终都未使用过这个实例,会造成内存的浪费。
2、饿汉式(静态代码块)
1 | public class Singleton{ |
这种方式跟第一种方式类似,都是在类加载的时候完成的,只不过将实例化的过程放在了静态代码块种,优缺点跟上面一样。
3、懒汉式(线程不安全)
1 | public class Singleton{ |
这种写法在单线程环境下可以使用,但是多线程环境下显然会产生多个实例。
优点:写起来比较简单,当类Singleton被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存;
缺点:并发环境下很可能出现多个Singleton实例。
4、懒汉式(线程安全,同步方法)
1 | public class Singleton{ |
由于每次去获取实例的时候都会进入synchronized代码块而不管实例是否为null,而其实这个方法只需要执行一次实例化代码就可以,因此这样的开销非常大,所以不推荐使用。
优点是:使用synchronized关键字避免多线程访问时,出现多个Singleton实例。
缺点是:同步方法频繁调用时,效率略低。
5、懒汉式(线程安全,同步代码块)
1 | public class Singleton{ |
这种同步并不能起到线程同步的作用,跟第三种方式遇到的情形一致。假如两个线程同时进入了if(instance == null)代码块,第一个线程还未进行处理,另外一个线程也通过了判断,这样会产生多个实例,因此同样不推荐使用。
6、双重检查锁
1 | public class Singleton{ |
双重检查锁对于多线程开发者来说并不陌生,我们进行了两次if(singleton == null)判断,并通过将实例singleton设置为volatile变量,这样可以实现变量的可见性并且禁止编译器指令重排序造成的其它问题。
优点:线程安全,延迟加载,效率较高。
7、静态内部类
1 | public class Singleton{ |
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading(懒加载)的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
8、枚举
1 | public enum Singleton{ |
优点:系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
缺点:当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,使用枚举实现单例模式很少出现。