设计模式5,单例模式(上)

目录

普通懒汉式

DoubleCheck双重检查

静态内部类

饿汉式


保证一个类仅有一个实例,并提供一个全局访问点。

类型:创建型

使用场景:想确保任何情况下都绝对只有一个实例

优点:在内存里只有一个实例,减少了内存的开销。可以避免对资源的多重占用。设置全局访问点,严格控制访问。

缺点:没有接口,扩展困难,想扩展只能修改代码。

重点:私有构造器(禁止从单例模式外部调用构造函数),线程安全,延迟加载,序列化和反序列化安全,反射


普通懒汉式

public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    private LazySingleton(){

    }
    public static LazySingleton getInstance(){
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

关于懒汉式,顾名思义是延迟加载。这种模式对于单线程来说没有任何问题,但是对于多线程的话,就可能会出问题。

假如代码中线程1执行到了第8行,线程2来到了第7行,线程1这是还没有new对象,线程2中的if自然是true,这样两个线程都会new对象,就破坏了单例。

public class T implements Runnable {
    @Override
    public void run() {
        LazySingleton lazySingleton = LazySingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + "  " + lazySingleton);
    }
}
public class Test {
    public static void main(String[] args) {
        Thread t1 = new Thread(new T());
        Thread t2 = new Thread(new T());
        t1.start();
        t2.start();
        System.out.println("program end;");
    }
}

通过线程debug,当线程1到这一行的时候

线程2也进来了 

此时由于线程1还没有new,所以线程2往下走可以进if中,再回到线程1往下走

对象hash为538,线程2往下走

变成了539,放开断点

虽然运行结果都一样,但是通过debug我们可以看出,实际上是产生了两个对象,只不过后产生的覆盖了前面产生的而已。要解决这样的问题有几种办法:

1.加synchronized,顺嘴提一句,synchronized修饰static方法,锁的是这个class文件本身,修饰普通方法,锁的是栈中的对象

public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    private LazySingleton(){

    }
    public synchronized static LazySingleton getInstance(){
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

如此,当线程1在方法中的时候,线程2是阻塞状态,没有办法进方法的。当然由于同步锁的方式,有上锁和解锁的步骤,所以对内存开销比较大。于是演进出了下面一种方式 :


DoubleCheck双重检查

public class LazyDoubleCheckSingleton {
    private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
    private LazyDoubleCheckSingleton(){

    }
    public static LazyDoubleCheckSingleton getInstance(){
        if(lazyDoubleCheckSingleton == null){
            synchronized (LazyDoubleCheckSingleton.class){
                if(lazyDoubleCheckSingleton == null){
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}

将synchronized缩小到方法体中,这样,就减少了内存开销,但这样也是有隐患的,在内存初始化的时候会有几个步骤:

1.分配内存给对象

2.初始化对象

3.设置instance指向刚分配的内存地址

4.访问对象

在单线程中,为了使性能更好,2和3是可以互换位置的(重排序)。但在多线程中,线程1中假如2和3互换了位置,在线程1设置instance指向内存空间后(此时还没有初始化),线程2进来开始判断instance是否是null,由于此时已经分配了内存空间,所以不为null,所以线程2开始访问对象,但是因为线程1还没有开始初始化对象,所以线程2访问的是有问题的。

所以为了解决这个问题,我们只需要加volatile关键词,这样重排序就会禁止了。

当然,我在线程1的创建过程中,不让线程2看到细节,线程2就不能访问了,于是有了下面的版本


静态内部类

public class StaticInnerClassSingleton {
    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton;
    }

    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }

    private StaticInnerClassSingleton(){

    }
}

JVM在类被初始化阶段(class被加载后,被线程使用之前),jvm会获取一个锁,用来同步多个线程对一个类的初始化。

类会在这几种情况下被初始化:

1.该类的实例被创建

2.类中声明的静态方法被调用

3.类中声明的静态成员被赋值

4.类中声明的静态成员被使用,且这个成员不是常量成员

5.该类是顶级类,且有嵌套的断言语句

所以关于静态内部类,就是使用了jvm的特性,只有一个线程可以拿到class的初始化锁,拿到初始化锁的就去获取对象。


饿汉式

public class HungrySingleton {
    private final static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){

    }

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

这种写法很简单,在类加载的时候就初始化好了,这样也就避免了线程同步的问题,当然如果用不到,会造成资源的浪费。还可以写在静态代码块中:

public class HungrySingleton {
    private final static HungrySingleton hungrySingleton = new StaticInnerClassSingleton();

    static {
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){

    }

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值