浅谈理解单例模式,从其他角度剖析(上)

浅谈理解单理模式(上)

在这篇文章里,你也许会得到对单例模式的一些其他方面认识,我会浅显的谈一谈我个人理解为什么会有单例模式,以及几种单例模式的一些形成的原理。对于各单例模式的方式以及优缺点,已经有很多大神进行了深度剖析。我这里就不比再去抛砖引玉,一些与文章无关的细节就不再去讲解,因此对于初次了解的单例模式的可以先通过其他文章了解一下单例模式的一些其他方面的细节。由于个人理解有限,对于不足或不对的地方,望大家多多指正。

对象的创建过程

当创建一个对象,虚拟机需要执行为校验类是否加载,对象分配内存,初始化,以及创建对象头,执行构造方法等一系列的操作。当我们将实例进行共享,使用已经有的实例时,仅仅需要传递已有实例的引用,大大节省了创建对象造成的开销,以及性能消耗。

浅谈为什么使用单例

假设我们有两个类。一个类Person和一个工具斧头类Axe,在Person的work方法中使用斧头。那我们可以创建两个类如下:

class Person{
    void work() {
        new Axe().use();
    }
}
class Axe{
	void use() {
		System.out.println("斧头被使用");
	}
}

这样的话我们就能开始使用斧头开始工作了。可是每当我们需要work一次,就要使用一次斧头,Axe就会被创建一次,这就会产生创建对象带来的开销以及性能消耗。那么我们可以将Axe添加到Person的属性当中(这里我我们不去考虑当改变工具所带来的依赖问题)。

class Persom{
	private Axe axe;
	void work() {
		//axe只被创建了一次。
    	axe.use();
    }
    public void setAxe(Axe axe) {
    	this.axe=axe;
    }
}

这样的话,只需设置一次Axe,我们无论使用几次work,都不会造成多次创建Axe的对象。可是这个方案真的好么?如果这个Person需要用到很多的工具或者物品,比如钳子、汽车、手机等。我将这些物品工具等都添加到Person属性中,显然不是明智的选择。
一个Person应该尽可能的只有于Person相关的属性,比如年龄、性别、姓名等,不应该过多的添加不相关的属性。如果Person不会衍生出派生类,Person添加了这些属性,还能勉强使用。但是继承会增强耦合,基类的属性会侵入到派生类中。比如Person的派生类Student学生类,显然不会使用到这些属性。这是单例模式的有点体现了出来。我们只需在方法执行的时候得到唯一的实例,并不会在类中添加一些冗余的属性,这样也就不会造成基类的这些属性对派生类的侵入。

//因为只是为了阐述使用单例模式的优点,
//这里只是简单使用了饿汉的单例模式。
class Axe {
    private static Axe axe=new Axe();
    private Axe(){};
    void use() {
        System.out.println("使用了斧头");
    }
    public static Axe getInstance() {
        return axe;
    }
}
class Person{
    void work() {
        Axe.getInstance().use();
    }
}

浅谈单例模式形成的原理

单例模式的设计有很多种,又分为懒汉模式和饿汉模式。这里先从饿汉模式说起。

饿汉模式:使用类静态成员变量

class Single{
    private static Single single=new Single();
    private Single(){};
    public static Single getInstance() {
        return single;
    }
}

这种方式的重点是static。创建一个对象,jvm所经历的过程为:类加载过程->创建对象->执行对象的构造方法。静态变量的赋值是在类加载的过程中的类初始化阶段完成的。类的加载过程JVM中只执行一次,因此保证了实例对象的赋值只有一次。

懒汉模式:使用DLC双检查锁

class Single{
    private static volatile Single single;
    private Single(){};
    public static Single getInstance() {
        if(single==null) {
            synchronized (Single.class) {
                if(single==null) {
                    single=new Single();
                }
            }
        }
        return single;
    }
}

这种方式的重点是双检查锁DLC和volatile。1.DLC是延迟加载常用的一种方式,既能够保证了延迟加载,又能够保证了线程之间的同步问题(因为饿汉模式是在类加载阶段完成初始化,因此不存在线程安全问题)。2.volatile是为了防止得到一个半初始化的对象。当创建一个对象的时候,过程为检查类是否被加载->为对象分配内存->初始化对象->创建对象头->执行构造方法->将对象的地址赋值给变量。JVM为了提高效率会在不存在数据依赖的情况下进行指令级优化重排序,对象的地址赋值和执行勾造方法可能会被重排序。因此当其他线程进行Single==null的判断时候,可能会得到一个还未执行构造方法的对象。而volatile能够禁止指令之间的重排序。

懒汉模式:静态内部类

class Single{
    private static class InnerClass {
        static final Single SINGLE=new Single();
    }
    private Single(){};
    public static Single getInstance() {
        return InnerClass.SINGLE;
    }
}

这种方式的重点是静态内部类。首先定义了静态的变量属性与前面道理一样,保证了静态变量只会初始化一次。类初始化是对类属性,静态代码块的初始化,因此静态内部类InnerClass不会在外部类Single的加载阶段进行初始化。那么InnerClass的初始话是在什么时候加载的呢?类的加载触发的条件:1.调用静态变量或者静态方法(常量不会,在编译阶段变成了字面量)2.子类加载必须先加载父类。3.优先加载main方法的类。InnerClass.SINGLE;这里触发类静态内部类InnerClass的初始化。因此保证类类的延迟加载。
那么静态内部类是如何保证了线程的安全性?由于类加载的时候,JVM必须保证类只能够被加载一次,因此在类加载阶段,JVM会进行上锁。因此保证了同步。

下一篇文章我将会继续浅谈懒汉模式中枚举方式、静态代码块的实现原理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值