深入理解设计模式之单例模式

单例模式看着一篇就够了
问题:
多个线程操作不同实例对象,现在需要多个线程要操作同一对象,要保证对象的唯一性。
解决问题:
实例化过程中只实例化一次

单例模式属于创造型模式,是常用的设计模式之一。

单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。

如何区分饿汉式和懒汉式:在自己被加载时就将自己实例化,称之为饿汉式单例类,而要是在第一次被引用时,才会将自己实例化,称之为懒汉式。

饿汉式

线程安全:在加载的时候(ClassLoader) 已经被实例化,所以只有一次,线程安全的。
懒加载:没有延迟加载,对于长时间不使用,影响性能,如果占的内存多会导致内存溢出
性能:比较好

package sheji;

/**
 * @author zhr
 * @create 2018-11-15 下午4:02
 */
public class Singleton {
	//static 在加载的时候就产生了实例对象
    private static Singleton instance = new Singleton();

    private Singleton(){

    }

    public static Singleton getInstance(){
        return instance;
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("这两个对象是相同的实例");
        }
    }
}
控制台打印:这两个对象是相同的实例

懒汉式

线程安全:不能保证实例对象的唯一性,可能出现多个线程同时进入到instance==null中 生成多个对象(一票否决不要用)
懒加载:有延迟加载
性能:好
跟饿汉式比:优化了在调用过程的时候才去实例化,但又出现了新的问题那就是线程安全的问题


/**
 * @author zhr
 * @create 2018-11-15 下午3:59
 */
public class Singleton {

    private static Singleton instance;

    private Singleton(){

    }

    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("这两个对象是相同的实例");
        }
    }
}

控制台打印:这两个对象是相同的实例

懒汉式+同步方法

线程安全:可以保证实例唯一性
懒加载:有延迟加载
性能:不好,因为使用了synchronized 多个线程访问的会后就会蜕化到了串行执行
跟懒汉式比:线程安全了,但是锁的粒度太大性能损失很大


/**
 * @author zhr
 * @create 2018-11-15 下午4:00
 */
public class Singleton {

    private static Singleton instance;

    private Singleton(){

    }

    public synchronized static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("这两个对象是相同的实例");
        }
    }
}

控制台打印:这两个对象是相同的实例

懒汉式+同步代码块

线程安全:不能保证实例唯一
懒加载:有延迟加载
性能:一般
懒汉式+同步方法比较:我们锁的粒度虽然减小了,但是在并发的环境下当,多个线程同时进入到instance==null的时候第一个实例创建对象,释放锁的时候第二个线程可能就会创建对象,从而导致线程不安全


/**
 * @author zhr
 * @create 2018-11-15 下午4:07
 */
public class Singleton {

    private static Singleton instance;

    private Singleton(){

    }

    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                instance = new Singleton();  
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("这两个对象是相同的实例");
        }
    }
}
控制台打印:这两个对象是相同的实例

双重校验锁 DCL

线程安全:可以保证实例唯一性
懒加载:有延迟加载
性能:比较好
问题:因为happens-before指令重排,里面有多个引用对象需要初始化的时候,会出现空指针异常


/**
 * @author zhr
 * @create 2019-09-14 下午3:08
 */
public class Singleton {

    private static Singleton instance;
    Connection conn;
    Socket socket;

    private Singleton(){
		//初始化conn 和 socket 这里指令重排 把初始化放在前面了,导致第一个线程初始化了,第二个线程进来认为已经初始化了,然后获取了conn就会出现空指针异常
    }

    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("这两个对象是相同的实例");
        }
    }
}
控制台打印:这两个对象是相同的实例

Volatile+双重校验锁 DCL

线程安全:可以保证实例唯一性
懒加载:有延迟加载
性能:比较好(推荐使用)
解决DCL指令重排问题方案是机上volatile使初始化前的东西不能被指令重排就可以了。


/**
 * @author zhr
 * @create 2019-09-14 下午3:10
 */
public class Singleton {

    private volatile static Singleton instance;
    Connection conn;
    Socket socket;

    private Singleton(){

    }

    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("这两个对象是相同的实例");
        }
    }
}
控制台打印:这两个对象是相同的实例

Holder持有者

声明类的时候,成员变量中不声明实例变量,而放到内部静态类中,在内部静态类中我们提供实例化这个类,主要是为了避免加锁,只有主动使用的时候才会去进行实例化
线程安全:可以保证实例唯一性
懒加载:有延迟加载
性能:比较好(广泛使用)


/**
 * @author zhr
 * @create 2019-09-14 下午3:20
 */
public class HolderDemo{

	//可以删除
    private HolderDemo(){

    }
    
	private static class Holder{
		//懒加载 没加锁
		private static HolderDemo instance = new HolderDemo();
		
	}

	public static HolderDemo getInstance(){
		return Holder.instance;
	}

}
控制台打印:这两个对象是相同的实例

枚举

线程安全:可以保证实例唯一性
懒加载:没有延迟加载
性能:比较好(广泛使用)
评价:,其实跟饿汉式很像,代码简洁优雅


/**
 * @author zhr
 * @create 2019-09-14 下午3:25
 */
public enum EnumSingleton{
	//常量,在加载的时候被实例化一次
	INSTANCE;

	public static EnumSingleton getInstance(){
		return INSTANCE;
	}

}

枚举+Holder

线程安全:可以保证实例唯一性
懒加载:有延迟加载
性能:比较好(广泛使用)


/**
 * @author zhr
 * @create 2019-09-14 下午3:30
 */
public class EnumSingletonDemo{

	private EnumSingletonDemo(){

	}
	
	//静态内部类 只有在使用的时候才会加载实例化
	private enum EnumHolder{
		//常量,在加载的时候被实例化一次,方法区
		INSTANCE;

		private EnumSingletonDemo instance;
		
		EnumHolder(){	
			this.instance = new EnumSingletonDemo();
		}

		private EnumSingletonDemo getInstance(){
			return instance;
		}
	}//懒加载

	public static EnumSingletonDemo getInstance(){
		return EnumHolder.INSTANCE.getInstance();
		//return  EnumHolder.INSTANCE.instance;
	}
}

如果看完感觉受益匪浅,可以帮忙点点关注,点点赞,还会持续更新好的作品。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章