您的当前位置:首页正文

设计模式:单例模式

2024-11-29 来源:个人技术集锦

1.应用场景:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2.spring中的单例:Spring 中的单例模式完成了后半句话,即提供了全局的访问点 BeanFactory。但没有从构造器级别去
控制单例,这是因为 Spring 管理的是是任意的 Java 对象。 Spring 下默认的 Bean 均为单例。

3.特点:保证从系统启动到系统终止,全过程只会产生一个实例。
4.应用:当我们在应用中遇到功能性冲突的时候,需要使用单例模式

5.举例:配置文件、日历、IOC 容器

6.实现方式:

1)饿汉式 :它是在类加载的时候就立即初始化,并且创建单例对象;

优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好

缺点:类加载的时候就初始化,不管你用还是不用,我都占着空间 ,浪费了内存

绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题

public class Hungry {

    private Hungry(){}
    //先静态、后动态
    //先属性、后方法
    //先上后下
    private static final Hungry hungry = new Hungry();

    public static Hungry getInstance(){
//        Hungry hungry;

        return  hungry;
    }

}

 

2)懒汉式:

优点:在外部需要使用的时候才进行实例化,节省了空间

缺点:线程不安全。再有多线程同时访问时候会出现new出了新对象的情况

//懒汉式单例

    
public class LazyOne {
    private LazyOne(){}


    //静态块,公共内存区域
    private static LazyOne lazy = null;

    public static LazyOne getInstance(){

        //调用方法之前,先判断
        //如果没有初始化,将其进行初始化,并且赋值
        //将该实例缓存好
        if(lazy == null){
            //两个线程都会进入这个if里面
            lazy = new LazyOne();
        }
        //如果已经初始化,直接返回之前已经保存好的结果

        return lazy;

    }

}

解决懒汉式线程不安全的问题:

1.方法加同步锁:效率差

public class LazyTwo {

    private LazyTwo(){}

    private static LazyTwo lazy = null;

    public static synchronized LazyTwo getInstance(){

        if(lazy == null){
            lazy = new LazyTwo();
        }
        return lazy;

    }

}

2.解决懒汉式线程不安全的最优方案:

这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题

利用的知识:

1.在外部类被调用的时候内部类才会被加载,内部类一定是要在方法调用之前初始化

2.懒汉式 + 恶汉式

public class LazyThree {

    private boolean initialized = false;

    //默认使用LazyThree的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的

    private LazyThree(){

        synchronized (LazyThree.class){
            if(initialized == false){
                initialized = !initialized;
            }else{
                throw new RuntimeException("单例已被侵犯");
            }
        }

    }


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


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


}

 

3)注册式:注册式是spring中最常用的单例获取方式,他使用了ConcurrentHashMap,线程安全

//Spring中的做法,就是用这种注册式单例
public class BeanFactory {

    private BeanFactory(){}

    //线程安全
    private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();

    public static Object getBean(String className){

        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);
        }

    }


}

4)序列化:反序列化的过程会破坏单例,生成新对象,如果希望继续使用源对象:

public class Seriable implements Serializable {


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

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


    public  final static Seriable INSTANCE = new Seriable();
    private Seriable(){}

    public static  Seriable getInstance(){
        return INSTANCE;
    }

    private  Object readResolve(){
        return  INSTANCE;
    }

}

重写了readResolve方法,反序列化后就会得到序列化的对象了,测试代码:

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

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

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("Seriable.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();


            FileInputStream fis = new FileInputStream("Seriable.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (Seriable)ois.readObject();
            ois.close();

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

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

5)枚举:直接帖代码

//常量中去使用,常量不就是用来大家都能够共用吗?
    //通常在通用API中使用
public enum Color {
    RED(){
       private int r = 255;
       private int g = 0;
       private int b = 0;

    },BLACK(){
        private int r = 0;
        private int g = 0;
        private int b = 0;
    },WHITE(){
        private int r = 255;
        private int g = 255;
        private int b = 255;
    };
}

测试类:

public class ColorTest {
    public static void main(String[] args) {
        System.out.println(Color.RED);
    }
}

以上。

显示全文