单例模式

目录

什么是单例模式

单例模式的八种形式 

饿汉式(静态常量) 

饿汉式(静态代码块) 

懒汉式(线程不安全)

懒汉式(线程安全)

懒汉式(同步代码块)

☆双重检查volatile(DCL)

静态内部类

☆枚举

单例模式在JDK源码的应用

单例模式的总结


 

什么是单例模式

 

采用一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法) 

 

 

单例模式的八种形式 

 

 

 

饿汉式(静态常量) 

 

直接创建对象,不存在线程安全问题

public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }

}

class Singleton{
    //1.构造器私有化,外部不能new
    private Singleton(){
    }

    //2.本类内部创建对象实例
    private final static Singleton instance = new Singleton();

    //3.提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
}

总结:这种方法在类装载就完成初始化,如果单例的对象没有使用,就会造成内存浪费

 

 

饿汉式(静态代码块) 

 

只有第二步做了改动

 

 

懒汉式(线程不安全)

 

延迟创建对象

public class SingletonTest03 {
    public static void main(String[] args) {
        Singleton3 instance1 = Singleton3.getInstance();
        Singleton3 instance2 = Singleton3.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton3{
    private static Singleton3 instance;

    private Singleton3(){}

    public static Singleton3 getInstance(){
        if(instance == null) {
            instance = new Singleton3();
        }
        return instance;
    }
}

总结:实现了懒加载即使用时才实例化对象,但只能在单线程下进行。原因是多线程下,一个线程进入了if判断,如果当还未来得及往下执行时,另一个线程也通过了这个判断,这时变产生了多个实例。生产中,不要使用这种方式

 

 

懒汉式(线程安全)

 

在返回对象实例的方法上,加上一个sychronized关键字,上锁

总结:这种方法虽然保证了多线程的安全性,但频繁创建实例就意味着频繁加锁判断,降低了效率。实际开发中也不推荐使用

 

 

懒汉式(同步代码块)

 

总结:并不能起到线程同步的作用,多线程下同样可能出现当第一个实例刚进入if判断,第二个实例就申请创建了。多线程下无法实现单例,不能使用

 

 

☆双重检查volatile(DCL)

 

public class SingletonTest06 {
    public static void main(String[] args) {
        Singleton6 instance1 = Singleton6.getInstance();
        Singleton6 instance2 = Singleton6.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton6{
    private static volatile Singleton6 instance;

    private Singleton6(){}

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

instance = new Singleton6();这一步并不是一个原子的操作。它分为三步:1.分配内存空间  2.执行构造方法,初始化对象   3.把这个对象指向这个内存空间。只要不是原子性操作,就可能会有指令重排的现象,因此必须加volatile 

总结:使用了volatile轻量级锁,它的双重检查核心其实是禁止了指令重排,这样在多线程第二次判断遇到sychornized时,instanceo变量instance前后产生了内存屏障,正在执行的线程会先执行完毕,之后的线程不会在之前先执行完。这种方式推荐

 

 

 静态内部类

 

public class SingletonTest07 {
    public static void main(String[] args) {
        Singleton7 instance1 = Singleton7.getInstance();
        Singleton7 instance2 = Singleton7.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}

class Singleton7{
    private static volatile Singleton7 instance;

    private Singleton7(){ }

    //写一个静态内部类,该类中有一个静态属性Singleton
    private static class SingletonInstance{
        private static final Singleton7 INSTANCE = new Singleton7();
    }

    //提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE;
    public static synchronized Singleton7 getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

总结:这种方式采用了类装载机制保证了初始化实例时只有一个线程。静态内部类在Singleton类装载时不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,因此JVM帮我们保证了线程的安全性。在类继续初始化时,别的线程无法进入。

优点:避免了线程不安全,静态内部类特点实现了延迟加载,效率高。推荐

 

 

☆枚举

 

public class SingletonTest08 {
    public static void main(String[] args) {
        Singleton8 instance1 = Singleton8.INSTANCE;
        Singleton8 instance2 = Singleton8.INSTANCE;
        System.out.println(instance1==instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
        instance1.sayOK();
        instance2.sayOK();
    }
}

//使用枚举,实现单例。强烈推荐
enum Singleton8 {
    INSTANCE;   //属性
    public void sayOK(){
        System.out.println("ok~");
    }
}

 总结:使用枚举来实现单例不仅能避免多线程同步的问题,而且还能防止反序列化重新创建新的对象。非常推荐使用

 

 

单例模式在JDK源码的应用

 

java.lang.Runtime就是用了饿汉式的单例模式,可以看到程序执行每次都会初始化Runtime方法,因此使用饿汉式不会对性能产生影响。

 

 

单例模式的总结

 

1.单例模式保证了系统内存中该类只有一个对象,节省了系统资源,对频繁创建销毁的对象,单例模式提高了它们的性能

2.想实例化一个单例类时,不能使用new的方式 

3.单例模式应用在需频繁创建和销毁的对象上,创建对象时耗过多或耗费资源过多的重量级对象上,例如工具类对象,数据源,session工厂等

 

注意:上述八种单例模式,最安全的只有Enum,因为其他七种都可以被反射破解,加标志位也没用,反射可以获取这个标志位的对象属性。只有枚举,反射无法破解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值