单例模式

设计模式-单例模式

一、定义:
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。
二、优点:
1.某些类创建比较繁琐,对于大型的项目,这是很大的开销。
2.省去new 操作符,降低系统内存的使用频率,减轻GC(垃圾回收器)压力。
3.有些交易所的核心交易引擎,控制着交易流程,只能创建一个,单例模型解决此问题。
三、单例模式:
1.饿汉模式:

public class Singleton1 {  
    /* 持有私有静态实例,防止被引用*/  
    private static Singleton1 instance = new Singleton1();  
    /* 私有构造方法,防止被实例化 */  
    private Singleton1() {}  

    /* 静态工程方法,返回Singleton实例 */  
    public static Singleton1 getInstance() {  
        return instance;  
    }  
    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    private Object readResolve() {  
        return instance;  
    }  
} 

问题:这个类是可以实现单例模式的,但是存在不少问题,比如在类中不管用户是否要使用该类的对象,就先创建好了一个实例放在内存中。
2.懒汉模式:

public class Singleton2 {  
    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */  
    private static Singleton instance = null;  
    /* 私有构造方法,防止被实例化 */  
    private Singleton() {}  

    /* 静态工程方法,创建实例 */  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    private Object readResolve() {  
        return instance;  
    }  
} 

问题:这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题。
3.解决多线程问题(加锁)

public class Singleton3 {  
    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */  
    private static Singleton3 instance = null;  

    /* 私有构造方法,防止被实例化 */  
    private Singleton3() {}  

    /* 静态工程方法,创建实例 */  
    public static synchronized Singleton3 getInstance() {  
        if (instance == null) {  
            instance = new Singleton3();  
        }
        return instance;  
    }   

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    public Object readResolve() {  
        return instance;  
    }  
} 

问题:多线程只是可能在对象没产生时产生问题,一旦对象产生,其他线程调用是不需要锁的。synchronized作为修饰符在方法上使用,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。
4.上锁的改进:

public class Singleton4 {  
    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */  
    private static Singleton4 instance = null;  
    /* 私有构造方法,防止被实例化 */  
    private Singleton4() {}  
    /* 静态工程方法,创建实例 */  
    public static Singleton4 getInstance() {  
        if (instance == null) { 
            synchronized (Singleton4.class) {  
                if (instance == null) {  
                    instance = new Singleton4();  
                }  
            }  
        }  
        return instance;  
    }   
    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    public Object readResolve() {  
        return instance;  
    }  
}

问题:似乎解决了之前提到的问题,将synchronized关键字加在了方法内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的。
看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句并非是一个原子操作,在 JVM 中这句代码大概做了下面 3件事情:
(1)给 new的对象 分配内存
(2)调用 Singleton 的构造函数来初始化成员变量
(3)将引用instance指向分配的内存空间(执行完这步 instance 就为非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,另外一个线程B抢夺到了CPU的执行权,这时instance已经是非null了(但却没有初始化),所以线程B会直接返回 instance,然后使用,结果就会出现问题了(因为对象还没有初始化)。
5.对非原子操作的改进:
解决方案:使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的(就是加载完毕后别的线程才能使用)。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。

public class Singleton5 {  
    /* 私有构造方法,防止被实例化 */  
    private Singleton5() {}  
    /* 此处使用一个内部类来维护单例 */  
    private static class SingletonFactory {  
        private static Singleton5 instance = new Singleton5();  
    }  

    /* 获取实例 */  
    public static Singleton5 getInstance() {  
        return SingletonFactory.instance;  
    }  
    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    public Object readResolve() {  
            return getInstance();  
    }  
}

问题:但是如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。例如,我们还可以使用反射去创建这个类的对象,即使它的构造器是私有的,我们也是可以调用到的。那么这个时候我们就需要再次修改代码去访问别人反射调用构造器。
6.对反射获取新对象的改进:

public class Singleton6 {  
    //标记位
    private volatile static boolean flag;
    /* 私有构造方法,防止被实例化 */  
    private Singleton6() {  
        if(!flag){
            flag = true;
        }else{
            throw new RuntimeException("不能多次创建单例对象");
        }
    }  
    /* 此处使用一个内部类来维护单例 */  
    private static class SingletonFactory {  
        private static Singleton6 instance = new Singleton6();
    }  
    /* 获取实例 */  
    public static Singleton6 getInstance() {  
        return SingletonFactory.instance;  
    }  
    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    private Object readResolve() {  
        return getInstance();  
    }  
} 

问题:反射的问题处理完了之后,这里还有一个问题,就是如果把单例对象进行序列化然后再反序列化,那么内存中就会出现俩个一样的单例对象,只是内存地址同。这种情况我们可以使用readResolve方法来防止。
7.序列化和反序列化解决

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Singleton7 implements Serializable{  
    private static final long serialVersionUID = 7863921642936733566L;

    private volatile static boolean flag;
    /* 私有构造方法,防止被实例化 */  
    private Singleton7() {  
        if(!flag){
            flag = false;
        }else{
            throw new RuntimeException("不能多次创建单例对象");
        }
    }
    /* 此处使用一个内部类来维护单例 */  
    private static class SingletonFactory {  
        private static Singleton7 instance = new Singleton7();
    }  
    /* 获取实例 */  
    public static Singleton7 getInstance() {  
        return SingletonFactory.instance;  
    }  
    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    /* 把这个方法注释前和注释后来运行测试代码观察结果 */  
    private Object readResolve() {  
        return getInstance();  
    }  
}  

测试代码:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Singleton7 implements Serializable{  
    private static final long serialVersionUID = 78923616429237696L;

    private volatile static boolean flag;
    /* 私有构造方法,防止被实例化 */  
    private Singleton7() {  
        if(!flag){
            flag = false;
        }else{
            throw new RuntimeException("不能多次创建单例对象");
        }
    }

    /* 此处使用一个内部类来维护单例 */  
    private static class SingletonFactory {  
        private static Singleton7 instance = new Singleton7();
    }  

    /* 获取实例 */  
    public static Singleton7 getInstance() {  
        return SingletonFactory.instance;  
    }  


    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    /* 把这个方法注释前和注释后来运行测试代码观察结果 */  
    private Object readResolve() {  
        return getInstance();  
    }  
}  
基于粒子群优化算法的p-Hub选址优化(Matlab代码实现)内容概要:本文介绍了基于粒子群优化算法(PSO)的p-Hub选址优化问题的研究与实现,重点利用Matlab进行算法编程和仿真。p-Hub选址是物流与交通网络中的关键问题,旨在通过确定最优的枢纽节点位置和非枢纽节点的分配方式,最小化网络总成本。文章详细阐述了粒子群算法的基本原理及其在解决组合优化问题中的适应性改进,结合p-Hub中转网络的特点构建数学模型,并通过Matlab代码实现算法流程,包括初始化、适应度计算、粒子更新与收敛判断等环节。同时可能涉及对算法参数设置、收敛性能及不同规模案例的仿真结果分析,以验证方法的有效性和鲁棒性。; 适合人群:具备一定Matlab编程基础和优化算法理论知识的高校研究生、科研人员及从事物流网络规划、交通系统设计等相关领域的工程技术人员。; 使用场景及目标:①解决物流、航空、通信等网络中的枢纽选址与路径优化问题;②学习并掌握粒子群算法在复杂组合优化问题中的建模与实现方法;③为相关科研项目或实际工程应用提供算法支持与代码参考。; 阅读建议:建议读者结合Matlab代码逐段理解算法实现逻辑,重点关注目标函数建模、粒子编码方式及约束处理策略,并尝试调整参数或拓展模型以加深对算法性能的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tachypsychia

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值