工厂模式之单例模式[scope="singleton"]

3.单例模式

3.1.饿汉式单例模式

1.在单例类第一次加载的时候就创建实例;不管用不用,先加载创建.

静态方法实例化

package com.gaoxinfu.demo.pattern.singleton.hungry;

public class HungrySingleton {
//先静态、后动态
//先属性、后方法
//先上后下
	private static final HungrySingleton hungrySingleton=new HungrySingleton();
	
	private HungrySingleton() {
		// TODO Auto-generated constructor stub
	}
	public static HungrySingleton getInstance() {
		return hungrySingleton;
	}
}
}

静态块实例化

package com.gaoxinfu.demo.pattern.singleton.hungry;

public class HungryStaticSingleton {

	private static final HungryStaticSingleton hungrySingleton;
	
	//静态块
	static{
		hungrySingleton=new HungryStaticSingleton();
	}
	
	private HungryStaticSingleton() {
		// TODO Auto-generated constructor stub
	}
	public static HungryStaticSingleton getInstance() {
		return hungrySingleton;
	}
}

3.1.2.优点

    //优点:没有加任何的锁、执行效率比较高,
    //在用户体验上来说,比懒汉式更好
    //绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题

3.1.2.缺点

    不管用不用都初始化了,有点浪费内存空间资源
    所以只能小范围使用

为了改造上面问题,出现懒汉式单例

3.2.懒汉式单例模式

3.2.1.概述

1.被外部类第一次调用时,才会初始化话创建;

3.2.2.案例说明

3.2.2.1 案例一

package com.gaoxinfu.demo.pattern.singleton.lazy;

/**
 * 
 * @Description:TODO
 * @Author:gaoxinfu
 * @Time:2019年3月10日 上午9:08:26
 */

//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {

    private LazySimpleSingleton(){}
    //静态块,公共内存区域
    //不能用final 声明,因为使用final,getInstance 方法 无法赋值
    private static LazySimpleSingleton lazy = null;
    public static LazySimpleSingleton getInstance(){
        if(lazy == null){
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

测试

package com.gaoxinfu.demo.pattern.singleton.lazy;

public class LazySimpleSingletonTest {

	public static void main(String[] args) {
		LazySimpleSingleton lazySimpleSingleton=LazySimpleSingleton.getInstance();
		System.out.println(lazySimpleSingleton);
	}
}

但是上面存在线程安全的问题,详细见案例二测试演示

3.2.2.2 案例二 线程安全问题演示

复制上面的 3.2.2.1

package com.gaoxinfu.demo.pattern.singleton.lazy;

/**
 * 
 * @Description:TODO
 * @Author:gaoxinfu
 * @Time:2019年3月10日 上午9:08:26
 */

//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {

    private LazySimpleSingleton(){}
    //静态块,公共内存区域
    //不能用final 声明,因为使用final,getInstance 方法 无法赋值
    private static LazySimpleSingleton lazy = null;
    public static LazySimpleSingleton getInstance(){
        if(lazy == null){
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}
package com.gaoxinfu.demo.pattern.singleton.lazy;

/**
 * 
 * @Description:TODO
 * @Author:gaoxinfu
 * @Time:2019年3月10日 上午9:19:39
 */
public class ExectorThread implements Runnable{
	
    @Override
    public void run() {
        LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" + singleton);
    }
}

测试

package com.gaoxinfu.demo.pattern.singleton.lazy;


public class LazySimpleSingletonThreadTest {

	  public static void main(String[] args) {
	        Thread t1 = new Thread(new ExectorThread());
	        Thread t2 = new Thread(new ExectorThread());
	        t1.start();
	        t2.start();
	        System.out.println("End");
	    }
}

演示如下

在这里插入图片描述
解释

扫描二维码关注公众号,回复: 6108685 查看本文章
1.LazySimpleSingleton类中getInstance方法中加一个断点;
2.我们启动了两个断点.Thread0 和Thread1 线程,可以针对断点进行调试,如上,
  选中,Thread0 ,然后进行一步一步的调试;然后调试Thread1 
3.完成之后,我们会发现两个线程实例化的对象不一致;
  很明显,上面的线程的问题,实例化出多个对象,不符合我们单例模式;
  解决上面的问题,3.2.2.3 案例三   	  

3.2.2.3 解决线程问题-优化1[方法上加锁]

package com.gaoxinfu.demo.pattern.singleton.lazy;

/**
 * 
 * @Description:TODO
 * @Author:gaoxinfu
 * @Time:2019年3月10日 上午9:08:26
 */

//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {

    private LazySimpleSingleton(){}
    //静态块,公共内存区域
    //不能用final 声明,因为使用final,getInstance 方法 无法赋值
    private static LazySimpleSingleton lazy = null;
    
    //在实例化方法上添加synchronized 锁,保证只能一个线程进来
    //但是这样的容易导致这个类被锁,所以,进一步优化,见3.2.2.4 
    public synchronized  static LazySimpleSingleton getInstance(){
        if(lazy == null){
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

3.2.2.3 解决线程问题-优化2[方法内对象双重加锁]

package com.gaoxinfu.demo.pattern.singleton.lazy;

public class LazyDoubleCheckSingleton {
	
    private volatile static LazyDoubleCheckSingleton lazy = null;
    private LazyDoubleCheckSingleton(){}
    public static LazyDoubleCheckSingleton getInstance(){
    	//一次验证
        if(lazy == null){
            //对象加锁实例化
            synchronized (LazyDoubleCheckSingleton.class){
                if(lazy == null){
                    lazy = new LazyDoubleCheckSingleton();
                    //1.分配内存给这个对象
                    //2.初始化对象
                    //3.设置lazy指向刚分配的内存地址
                    //4.初次访问对象
                }
            }
        }
        return lazy;
    }
}
第二步和第三方有可能会调换 这个就是由于指令排序的问题,volatile问题  
这个可以暂时忽略 后面分布式会讲解

双重加锁
在这里插入图片描述

3.2.2.4 静态内部类实例化[懒汉式]

package com.gaoxinfu.demo.pattern.singleton.lazy;

public class LazyInnerClassSingleton {
	
    private LazyInnerClassSingleton(){
    }

    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //final 保证这个方法不会被重写,重载
    public static final LazyInnerClassSingleton getInstance(){
        //在返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }

    //内部类 默认不加载
    private static class LazyHolder{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

备注

加载类的时候,优先加载内部类,加载内部类的时候,优先加载内部类的逻辑,整好实例化了final static 的对象
这个是性能最优的懒汉式写法

正常的调用LazyInnerClassSingleton.getInstance 是没有什么问题
但是如果通过反射,构造方法调用 还是存在着反射问题,如下:
package com.gupaoedu.vip.pattern.singleton.test;

import java.lang.reflect.Constructor;

import com.gupaoedu.vip.pattern.singleton.lazy.LazyInnerClassSingleton;


public class LazyInnerClassSingletonTest {

    public static void main(String[] args) {
        try{
            //很无聊的情况下,进行破坏
            Class<?> clazz = LazyInnerClassSingleton.class;

            //通过反射拿到私有的构造方法
            Constructor c = clazz.getDeclaredConstructor();
            
            c.setAccessible(true); //强制访问,对所有属性设置访问权限  当类中的成员变量为private时 必须设置此项

            //暴力初始化
            Object o1 = c.newInstance();

            //调用了两次构造方法,相当于new了两次
            //犯了原则性问题,
            Object o2 = c.newInstance();
            System.out.println(" o1  = " +o1);
            System.out.println(" o2  = " +o2);
            System.out.println(o1 == o2);
//            Object o2 = c.newInstance();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

结果如下,初始化了两个实例
在这里插入图片描述
为了防止这种通过反射去实例化对象的问题,我们改一下

package com.gaoxinfu.demo.pattern.singleton.lazy;

public class LazyInnerClassSingleton {
	
    private LazyInnerClassSingleton(){
        if(LazyHolder.LAZY != null){
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //final 保证这个方法不会被重写,重载
    public static final LazyInnerClassSingleton getInstance(){
        //在返回结果以前,一定会先加载内部类/
        return LazyHolder.LAZY;
    }

    //默认不加载
    private static class LazyHolder{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

在这里插入图片描述
再次测试,则会报不允许实例化两次
在这里插入图片描述

3.3.注册式单例模式

概念

1.将每个实例缓存到统一的容器中,使用标识符获取实例;

3.3.1.枚举式单例模式

案例

package com.gaoxinfu.demo.pattern.singleton.register;



//常量中去使用,常量不就是用来大家都能够共用吗?
//通常在通用API中使用
public enum EnumSingleton {
	
    INSTANCE;
    
    private Object data;
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

利用jad反编译EnumSingleton.class文件后如下: EnumSingleton.jad

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingleton.java

package com.gaoxinfu.demo.singleton.register;


public final class EnumSingleton extends Enum
{

    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }

    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(com/gaoxinfu/demo/singleton/register/EnumSingleton, name);
    }

    private EnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public Object getData()
    {
        return data;
    }

    public void setData(Object data)
    {
        this.data = data;
    }

    public static EnumSingleton getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingleton INSTANCE;
    private Object data;
    private static final EnumSingleton $VALUES[];

    static 
    {
        INSTANCE = new EnumSingleton("INSTANCE", 0);
        $VALUES = (new EnumSingleton[] {
            INSTANCE
        });
    }
}

package com.gaoxinfu.demo.pattern.singleton.register;


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class EnumSingletonTest {
	
    public static void main(String[] args) {
        try {
            EnumSingleton instance1 = null;

            EnumSingleton instance2 = EnumSingleton.getInstance();
            instance2.setData(new Object());

            FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(instance2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("EnumSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            instance1 = (EnumSingleton) ois.readObject();
            ois.close();

            System.out.println(instance1.getData());
            System.out.println(instance2.getData());
            System.out.println(instance1.getData() == instance2.getData());

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

我们通过看一下源码来佐证一下枚举式单例,如何调用

在这里插入图片描述
Enum<?> en = Enum.valueOf((Class)cl, name);
//通过(Class)cl, name参数确定一个枚举值
// 这行我们可以看出,只能一个,所以单例的

我们再来验证一下错误

package com.gaoxinfu.demo.pattern.singleton.register;

import java.lang.reflect.Constructor;


public class EnumSingletonTest {

    public static void main(String[] args) {
        try {
            Class clazz = EnumSingleton.class;
            Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
            c.setAccessible(true);
            EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("Tom",666);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

我们看一下源码
在这里插入图片描述

3.3.2.容器式单例模式

package com.gaoxinfu.demo.pattern.singleton.register;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

//Spring中的做法,就是用这种注册式单例
public class ContainerSingleton {
    private ContainerSingleton(){}
    private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
    public static Object getInstance(String className){
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object obj = null;
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            } else {
                return ioc.get(className);
            }
        }
    }
}

3.4.ThreadLocal单例模式

案例

package com.gaoxinfu.demo.pattern.singleton.threadlocal;


public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
            new ThreadLocal<ThreadLocalSingleton>(){
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };

    private ThreadLocalSingleton(){}

    public static ThreadLocalSingleton getInstance(){
        return threadLocalInstance.get();
    }
}

测试


package com.gaoxinfu.demo.pattern.singleton.threadlocal;

import com.gaoxinfu.demo.pattern.singleton.threadlocal.ThreadLocalSingleton ;


public class ExectorThread implements Runnable{
	
    @Override
    public void run() {
        ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" + singleton);
    }
}
package com.gaoxinfu.demo.pattern.singleton.threadlocal;

import com.gaoxinfu.demo.pattern.singleton.threadlocal.ThreadLocalSingleton ;
/**
 * Created by Tom.
 */
public class ThreadLocalSingletonTest {
    public static void main(String[] args) {

        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());

        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());
        t1.start();
        t2.start();
        System.out.println("End");

    }
}

在这里插入图片描述
不同的子进程,实例化不一样,典型的单例模式

关于ThreadLocal的介绍见
https://blog.csdn.net/u014636209/article/details/88571917

1.在内存中只有一个实例,减少了内存的开销;
2.可以避免对资源的多重应用
3.设置全局访问点,严格控制了访问

3.5.序列化单例模式

3.5.1.序列化概念

    序列化就是说把内存中的状态通过转换成字节码的形式
    从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO)
    内存中状态给永久保存下来了

    反序列化
    讲已经持久化的字节码内容,转换为IO流
    通过IO流的读取,进而将读取的内容转换为Java对象
    在转换过程中会重新创建对象new

3.5.2.案例1

3.5.2.1. 序列化对象

import java.io.Serializable;

//反序列化时导致单例破坏
public class SeriableSingleton implements Serializable {

    public  final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton(){

    }

    public static SeriableSingleton getInstance(){
        return INSTANCE;
    }
}

3.5.2.2. 测试1

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SeriableSingletonTest {
    public static void main(String[] args) {

        SeriableSingleton s1 = null;
        SeriableSingleton s2 = SeriableSingleton.getInstance();

        FileOutputStream fos = null;
        try {

            //输出一个对象属性
            fos = new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            //读取刚才写入的对象
            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SeriableSingleton)ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.5.2.3 输出结果

在这里插入图片描述

3.5.2.4 结果分析[不一致原因]

在这里插入图片描述

关键问题在于 s1 = (SeriableSingleton)ois.readObject();
详见下面动态图解释
在这里插入图片描述
描述
在这里插入图片描述
readObject()方法

在这里插入图片描述
readObject0方法

在这里插入图片描述
readOrdinaryObject 读取二进制文件

在这里插入图片描述

  boolean isInstantiable() {
        requireInitialized();
        return (cons != null);
    }
    //其中的变量cons声明  是否有构造方法,显然我们SeriableSingleton有构造方法

在这里插入图片描述
在这里插入图片描述
因此

            obj = desc.isInstantiable() ? desc.newInstance() : null;

因此会实例化出来新的对象;

在这里插入图片描述

    boolean hasReadResolveMethod() {
        requireInitialized();
        return (readResolveMethod != null);
    }

在这里插入图片描述
因为刚才我们测试的时候,并未自定义readResolve方法,所以会两个对象不一致;
在这里插入图片描述
通过反射初始化readResolveMethod

添加如下,重新即可
在这里插入图片描述
备注说明

1.重新发序列化方法,只是覆盖返序列化出来得对象,还是创建了2,发生在JVM层,相对来说比较安全;;
2.之前反序列化出来得对象会被GC回收

在这里插入图片描述

单例模式总结

1.优点

2.缺点

1.没有接口,扩展困难
2.如果扩展单例对象,只有修改代码,没有其他途径;

3.6 源码介绍

3.6.1 Runtime

//典型的饿汉式单例
//饿汉单例的话会影响JVM的启动速度。


public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
...

猜你喜欢

转载自blog.csdn.net/u014636209/article/details/88380759
今日推荐