单例设计模式
单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且只提供一个取得其对象的实例的方法。
实现步骤:
1.将该类的构造方法私有化,这样其他地方的代码就无法调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
2.在该类内提供一个公共静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。
八种写法:
- 静态常量饿汉式(可用)
/*
静态常量饿汉式
*/
public class HungryMan01 {
//构造器私有化
private HungryMan01(){}
//创建对象
private final static HungryMan01 HUNGRY_MAN_01 = new HungryMan01();
//向外暴露一个静态的公共方法 getInstance,返回实例对象
public static HungryMan01 getInstance(){
return HUNGRY_MAN_01;
}
}
- 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
- 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
2.静态代码块饿汉式(可用)
/*
静态代码块饿汉式
*/
public class HungryMan02 {
//构造器私有化
private HungryMan02(){}
private static HungryMan02 HUNGRY_MAN_02;
//静态代码执行时,创建单例对象
static {
HUNGRY_MAN_02 = new HungryMan02();
}
//向外暴露一个静态的公共方法 getInstance,返回实例对象
public static HungryMan02 getInstance(){
return HUNGRY_MAN_02;
}
- 这种方式和上面的方式类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。
- 优缺点和上面的类似
3.线程不安全懒汉式(不可用)
/*
线程不安全懒汉式
*/
public class LazyMan01 {
//1.构造器私有化
private LazyMan01(){}
//2.先不创建对象
private static LazyMan01 lazyMan01;
//3.向外暴露一个静态的公共方法 getInstance 当使用该方法时,才去创建实例对象
public static LazyMan01 getInstance(){
if (lazyMan01==null){
lazyMan01=new LazyMan01();
}
//4.返回实例对象
return lazyMan01;
}
}
- 这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。
- 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
4.线程安全(同步方法)懒汉式(不推荐用)
/*
线程安全懒汉式(同步方法)
*/
public class LazyMan02 {
//1.构造器私有化
private LazyMan02(){}
//2.先不创建对象
private static LazyMan02 lazyMan02;
//3.向外暴露一个静态的公共方法 getInstance 加入同步处理的代码,解决线程安全问题
// 当使用该方法时,才去创建实例对象
public static synchronized LazyMan02 getInstance(){
if (lazyMan02==null){
lazyMan02=new LazyMan02();
}
//4.返回实例对象
return lazyMan02;
}
}
- 解决上面第3种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法通过加入synchronized关键字进行了线程同步。
- 缺点:效率太低了,每个线程在想获得类的实例时,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
5.同步代码块(线程安全)懒汉式(不可用)
/*
同步代码块懒汉式(线程安全)
*/
public class LazyMan03 {
//1.构造器私有化
private LazyMan03(){}
//2.先不创建对象
private static LazyMan03 lazyMan03;
//3.向外暴露一个静态的公共方法 getInstance
// 当使用该方法时,才去创建实例对象
public static LazyMan03 getInstance(){
if (lazyMan03==null) {
synchronized (LazyMan03.class) {
lazyMan03 = new LazyMan03();
}
}
//4.返回实例对象
return lazyMan03;
}
}
- 由于第4种实现方式同步效率太低,所以摒弃同步方法,改为 同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。
- 跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton ==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
6.双重检测锁懒汉式(推荐用)
/*
同步代码块懒汉式(线程安全)
*/
public class LazyMan04 {
//1.构造器私有化
private LazyMan04(){}
//2.先不创建对象 添加volatile关键字 可保持一致性和可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
private volatile static LazyMan04 lazyMan04;
//3.向外暴露一个静态的公共方法 getInstance 双重锁检测模式的懒汉式单例:DCL懒汉式 解决线程安全问题,同时解决懒加载问题
// 当使用该方法时,才去创建实例对象
public static LazyMan04 getInstance(){
if (lazyMan04==null){
synchronized (LazyMan04.class){
if (lazyMan04==null){
lazyMan04=new LazyMan04(); //不是原子性操作
}
}
}
//4.返回实例对象
return lazyMan04;
}
}
-我们进行了两次if (singleton ==null)双重锁检查,这样就可以保证线程安全了。
- 实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
- 优点:线程安全;延迟加载;效率较高。
7.静态内部类(推荐用)
/*
静态内部类
*/
public class StaticInterior {
//1.构造器私有化
private StaticInterior(){}
//2.静态内部类内部创建单例对象
public static class Interior{
private static final StaticInterior STATIC_INTERIOR=new StaticInterior();
}
//3.向外暴露一个静态的公共方法 getInstance,返回实例对象
public static StaticInterior getInstance(){
return Interior.STATIC_INTERIOR;
}
}
- 这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
- 不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
- 优点:避免了线程不安全,延迟加载,效率高。
8.枚举(推荐用)
/*
枚举
*/
public enum Enum {//enum 本身也是一个Class类
INSTANCE;//属性
public Enum getInstance(){//方法
return INSTANCE;
}
}
- 借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
总结:
-
优点 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
-
缺点
当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
适用场合
- 需要频繁的进行创建和销毁的对象
- 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
- 工具类对象;
- 频繁访问数据库或文件的对象。
注意:反射会破坏单例模式!!
反射不能破坏枚举!!