单例模式

1、为什么有单例模式
单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例。

假如有一个有这么一个需求,有一个类A和一个类B它们共享配置文件的信息,在这个配置文件中有很多数据如下图
在这里插入图片描述
如上图所示现在类ConfigFile中存在共享的数据Num1,Num2,Num3等。假如在类A中修改ConfigFile中数据,在类A中应该有如下代码

ConfigFile configFile=new ConfigFile();
configFile. Num1=2;
这个时候configFile中的Num1=2,但是请注意这里是new ConfigFile是一个对象,想象一下在进行了上述操作后类B中进行如下操作

ConfigFile configFile=new ConfigFile();
System. out.println(“configFile.Num1=” +configFile.Num1);
即直接new ConfigFile();然后打印Num1,大家思考一下这时候打印出的数据为几?我想你应该知道它打印的结果是这样的:configFile.Num1=1;也就是说因为每次调用都创建了一个ConfigFile对象,所以导致了在类A中的修改并不会真正改变ConfigFile中的值,它所更改的只是在类A中说创建的那个对象的值。假如现在要求在类A中修改数据后,要通知类B,即在类A和类B中操作的数据是同一个数据,类A改变一个数据,类B也会得到这个数据,并在类A修改后的基础上进行操作,那么我们应该怎么做呢?看到这大家可能会说so easy,把ConfigFile中的数据设置为静态不就Ok了吗?对,有这种想法很好,这样做也没有错。但是我们都知道静态数据的生命周期是很长的,假如ConfigFile中有很多数据时,如果将其全部设成静态的,那将是对内存的极大损耗。所以全部设置成静态虽然可行但并不是一个很好的解决方法。那么我们应该怎么做呢?要想解决上面的问题,其实不难,只要能保证对象是唯一的就可以解决上面的问题,那么问题来了如何保证对象的唯一性呢?这样就需要用单例设计模式了。

2、单例模式的设计思想
(1)不允许其他程序用new对象。

因为new就是开辟新的空间,在这里更改数据只是更改的所创建的对象的数据,如果可以new的话,每一次new都产生一个对象,这样肯定保证不了对象的唯一性。

(2)在该类中创建对象

因为不允许其他程序new对象,所以这里的对象需要在本类中new出来

(3)对外提供一个可以让其他程序获取该对象的方法

因为对象是在本类中创建的,所以需要提供一个方法让其它的类获取这个对象。

那么这三步怎么用代码实现呢?将上述三步转换成代码描述是这样的

(1)私有化该类的构造函数

(2)通过new在本类中创建一个本类对象

(3)定义一个公有的方法,将在该类中所创建的对象返回

3、单例模式的写法

3.1单例模式的饿汉式[可用]

public class Singleton {

private static Singleton instance=new Singleton();
private Singleton(){};
public static Singleton getInstance(){
return instance;
}
}
访问方式

Singleton instance = Singleton.getInstance();

得到这个实例后就可以访问这个类中的方法了。

优点:从它的实现中我们可以看到,这种方式的实现比较简单,在类加载的时候就完成了实例化,避免了线程的同步问题。

缺点:由于在类加载的时候就实例化了,所以没有达到Lazy Loading(懒加载)的效果,也就是说可能我没有用到这个实例,但是它

也会加载,会造成内存的浪费(但是这个浪费可以忽略,所以这种方式也是推荐使用的)。

3.2单例模式的饿汉式变换写法[可用]

public class Singleton{

private static Singleton instance = null;

static {
instance = new Singleton();
}

private Singleton() {};

public static Singleton getInstance() {
return instance;
}
}
访问方式:

Singleton instance = Singleton.getInstance();

得到这个实例后就可以访问这个类中的方法了。

可以看到上面的代码是按照在2中分析的那三步来实现的,这中写法被称为饿汉式,因为它在类创建的时候就已经实例化了对象。其实4.2和4.1只是写法有点不同,都是在类初始化时创建对象的,它的优缺点和4.1一样,可以归为一种写法。

3.3单例模式的懒汉式[线程不安全,不可用]

public class Singleton {

private static Singleton instance=null;

private Singleton() {};

public static Singleton getInstance(){

if(instance==null){
instance=new Singleton();
}
return instance;
}
}
这种方式是在调用getInstance方法的时候才创建对象的,所以它比较懒因此被称为懒汉式。

在上述两种写法中懒汉式其实是存在线程安全问题的,喜欢刨根问题的同学可能会问,存在怎样的线程安全问题?怎样导致这种问题的?好,我们来说一下什么情况下这种写法会有问题。在运行过程中可能存在这么一种情况:有多个线程去调用getInstance方法来获取Singleton的实例,那么就有可能发生这样一种情况当第一个线程在执行if(instancenull)这个语句时,此时instance是为null的进入语句。在还没有执行instance=new Singleton()时(此时instance是为null的)第二个线程也进入if(instancenull)这个语句,因为之前进入这个语句的线程中还没有执行instance=new Singleton(),所以它会执行instance=new Singleton()来实例化Singleton对象,因为第二个线程也进入了if语句所以它也会实例化Singleton对象。这样就导致了实例化了两个Singleton对象。所以单例模式的懒汉式是存在线程安全问题的,既然它存在问题,那么可能有解决这个问题的方法,那么究竟怎么解决呢?对这种问题可能很多人会想到加锁于是出现了下面这种写法。

3.4懒汉式线程安全的[线程安全,效率低不推荐使用]

public class Singleton {

private static Singleton instance=null;

private Singleton() {};

public static synchronized Singleton getInstance(){

if(instance==null){
instance=new Singleton();
}
return instance;
}
}
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。

3.5单例模式懒汉式[线程不安全,不可用]

对于上述缺陷的改进可能有的人会想到如下的代码

public class Singleton7 {

private static Singleton instance=null;

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
其实这种写法跟4.3一样是线程不安全的,当一个线程还没有实例化Singleton时另一个线程执行到if(instance==null)这个判断语句时就会进入if语句,虽然加了锁,但是等到第一个线程执行完instance=new Singleton()跳出这个锁时,另一个进入if语句的线程同样会实例化另外一个Singleton对象,线程不安全的原理跟4.3类似。因此这种改进方式并不可行,经过大神们一步一步的探索,写出了懒汉式的双重校验锁。

3.6单例模式懒汉式双重校验锁[推荐用]

public class Singleton {
/**

  • 懒汉式变种,属于懒汉式中最好的写法,保证了:延迟加载和线程安全
    */
    private static Singleton instance=null;

private Singleton() {};

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

Singleton instance = Singleton.getInstance();

得到这个实例后就可以访问这个类中的方法了。

Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (instance== null)检查,这样就可以保 证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (instance== null),直接return实例化对象。

优点:线程安全;延迟加载;效率较高。

3.7内部类[推荐用]

public class Singleton{

private Singleton() {};

private static class SingletonHolder{
private static Singleton instance=new Singleton();
}

public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
访问方式

Singleton instance = Singleton.getInstance();

得到这个实例后就可以访问这个类中的方法了。

这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同

的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时

并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类,从而完成Singleton的实例化。

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是

无法进入的。

优点:避免了线程不安全,延迟加载,效率高。

3.8枚举[极推荐使用]

public enum SingletonEnum {

instance;

private SingletonEnum() {}

public void method(){
}
}
访问方式

SingletonEnum.instance.method();

可以看到枚举的书写非常简单,访问也很简单在这里SingletonEnum.instance这里的instance即为SingletonEnum类型的引用所以得到它就可以调用枚举中的方法了。

借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过,这种方式也是最好的一种方式,如果在开发中JDK满足要求的情况下建议使用这种方式。

参考:https://blog.csdn.net/q_all_is_well/article/details/82377917
转载请注明出处:http://blog.csdn.net/dmk877/article/details/50311791

Stkcd [股票代码] ShortName [股票简称] Accper [统计截止日期] Typrep [报表类型编码] Indcd [行业代码] Indnme [行业名称] Source [公告来源] F060101B [净利润现金净含量] F060101C [净利润现金净含量TTM] F060201B [营业收入现金含量] F060201C [营业收入现金含量TTM] F060301B [营业收入现金净含量] F060301C [营业收入现金净含量TTM] F060401B [营业利润现金净含量] F060401C [营业利润现金净含量TTM] F060901B [筹资活动债权人现金净流量] F060901C [筹资活动债权人现金净流量TTM] F061001B [筹资活动股东现金净流量] F061001C [筹资活动股东现金净流量TTM] F061201B [折旧摊销] F061201C [折旧摊销TTM] F061301B [公司现金流1] F061302B [公司现金流2] F061301C [公司现金流TTM1] F061302C [公司现金流TTM2] F061401B [股权现金流1] F061402B [股权现金流2] F061401C [股权现金流TTM1] F061402C [股权现金流TTM2] F061501B [公司自由现金流(原有)] F061601B [股权自由现金流(原有)] F061701B [全部现金回收率] F061801B [营运指数] F061901B [资本支出与折旧摊销比] F062001B [现金适合比率] F062101B [现金再投资比率] F062201B [现金满足投资比率] F062301B [股权自由现金流] F062401B [企业自由现金流] Indcd1 [行业代码1] Indnme1 [行业名称1] 季度数据,所有沪深北上市公司的 分别包含excel、dta数据文件格式及其说明,便于不同软件工具对数据的分析应用 数据来源:基于上市公司年报及公告数据整理,或相关证券交易所、各部委、省、市数据 数据范围:基于沪深北证上市公司 A股(主板、中小企业板、创业板、科创板等)数据整理计算
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值