设计模式--单例模式的8种实现(java实现)

首先介绍下单例模式的3个注意点:

1)单例类只能有一个实例;

2)单例类必须自己创建自己的唯一实例;

3)单例类必须给所有其他对象提供这一实例;

 

下面是8种实现的具体细节:

 

1.饿汉式(静态常量)

2.饿汉式(静态代码块)

3.懒汉式(线程不安全)

4.懒汉式(线程安全,同步方法)

5.懒汉式(线程不安全,同步方法)

6.双重检查

7.静态内部类

8.枚举

在开发时,如果是单线程,推荐使用第1、2实现;如果是多线程,推荐使用第6、7、8实现。

1.饿汉式(静态常量)

public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton s1=Singleton.getInstance();
        Singleton s2=Singleton.getInstance();

        System.out.println(s1==s2);
        System.out.println("s1.hashCode():"+s1.hashCode());
        System.out.println("s2.hashCode():"+s2.hashCode());
    }
}

class Singleton{
    private static final Singleton singleton=new Singleton();

    private Singleton(){}//构造器私有化,防止new

    public static Singleton getInstance(){
        return singleton;
    }
}

输出

true
s1.hashCode():356573597
s2.hashCode():356573597

分析:1)优点:这种写法比较简单,在类加载时就完成实例化,并且可以避免线程问题;

           2)缺点:在类加载时就实例化,没有达到lazy loading(懒加载)的效果。如果自始至终都没有用过这个实例,则会造成内存浪费;

           3)这种方式基于类加载机制避免了多线程问题,但是在单例模式中大多都是调用getInstance方法,除了调用getInstance方法外导致类加载的原因有很多(下面列举了一些),如果是别的原因导致类加载,这时就会创建实例,就没有达到懒加载的效果;

结论:这种单例模式可用,可能造成内存浪费。

造成类加载的原因:

           1)创建类的实例,也就是new一个对象;

           2)访问某个类或接口的静态变量,或者对静态变量赋值;

           3)调用类的静态方法;

           4)反射;

           5)初始化一个类的子类;

           6)JVM启动时标记的启动类。

2.饿汉式(静态代码块)

public class SingletonTest02 {
    public static void main(String[] args) {
        Singleton s1= Singleton.getInstance();
        Singleton s2= Singleton.getInstance();

        System.out.println(s1==s2);
        System.out.println("s1.hashCode():"+s1.hashCode());
        System.out.println("s2.hashCode():"+s2.hashCode());
    }
}

class Singleton{
    private static Singleton singleton;

    static{
        singleton=new Singleton();
    }

    private Singleton(){}//私有构造方法防止new

    public static Singleton getInstance(){
        return singleton;
    }
}

输出

true
s1.hashCode():356573597
s2.hashCode():356573597

分析:1)这种方式和上面的方式类似,只不过将类的实例化放在了静态代码块里,也是在类加载时就完成了实例化,优缺点和上面相同;

结论:这种单例模式可用,可能造成内存浪费。

3.懒汉式(线程不安全)

public class SingletonTest03 {
    public static void main(String[] args) {
        Singleton s1= Singleton.getInstance();
        Singleton s2= Singleton.getInstance();

        System.out.println(s1==s2);
        System.out.println("s1.hashCode():"+s1.hashCode());
        System.out.println("s2.hashCode():"+s2.hashCode());
    }
}

class Singleton{
    private static Singleton singleton;

    private Singleton(){}//私有构造方法防止new

    public static Singleton getInstance(){
        if(singleton==null){
            singleton=new Singleton();
        }
        return singleton;
    }
}

分析:1)起到了lazy loading的效果,但只能在单线程下使用;

           2)如果在多线程下使用,假如一个线程进入了if(singleton==null)判断语句,还未来得及往下执行(还未执行singleton=new Singleton();因此还未创建实例),另一个线程也通过了这个判断语句,则这两个线程会创建两个实例。因此在多线程环境下不能使用这种方式实现单例模式;

结论:在实际开发中,不要使用这种方式。

4.懒汉式(线程安全,同步方法)

public class SingletonTest04 {
    public static void main(String[] args) {
        Singleton s1= Singleton.getInstance();
        Singleton s2= Singleton.getInstance();

        System.out.println(s1==s2);
        System.out.println("s1.hashCode():"+s1.hashCode());
        System.out.println("s2.hashCode():"+s2.hashCode());
    }
}

class Singleton{
    private static Singleton singleton;

    private Singleton(){}//私有构造方法防止new

    public static synchronized Singleton getInstance(){//添加synchronized关键字
        if(singleton==null){
            singleton=new Singleton();
        }
        return singleton;
    }
}

分析:1)解决了线程安全问题;

           2)效率太低了,每个线程在想获得类的实例时,执行getInstance方法都要同步。而这个方法只要执行一次实例化代码就够了,后面的想获得实例,只需要return就行了。

结论:在实际开发中,不推荐这种方式。

5.懒汉式(线程不安全,同步代码块)

public class SingletonTest05 {
    public static void main(String[] args) {
        Singleton s1= Singleton.getInstance();
        Singleton s2= Singleton.getInstance();

        System.out.println(s1==s2);
        System.out.println("s1.hashCode():"+s1.hashCode());
        System.out.println("s2.hashCode():"+s2.hashCode());
    }
}

class Singleton{
    private static Singleton singleton;

    private Singleton(){}//私有构造方法防止new

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

分析:线程不安全,与方式3相似。

结论:不推荐使用。

6.双重检查

public class SingletonTest06 {
    public static void main(String[] args) {
        Singleton s1= Singleton.getInstance();
        Singleton s2= Singleton.getInstance();

        System.out.println(s1==s2);
        System.out.println("s1.hashCode():"+s1.hashCode());
        System.out.println("s2.hashCode():"+s2.hashCode());
    }
}

class Singleton{
    private static volatile Singleton singleton;

    private Singleton(){}//私有构造方法防止new

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

分析:1)双重检查是多线程中常用到的,进行两次检查,可以保证线程的安全;

           2)实例代码只需执行一次,后面再次访问时,第一个if条件将不满足,直接return实例,避免了反复进行方法同步;

结论:在实际开发中,推荐使用这种方式实现单例模式

另:双重检查为何要加volatile关键字,参考博客:volatile关键字在单例模式(双重校验锁)中的作用,注意看博主的评论。

7.静态内部类

public class SingletonTest07 {
    public static void main(String[] args) {
        Singleton s1= Singleton.getInstance();
        Singleton s2= Singleton.getInstance();

        System.out.println(s1==s2);
        System.out.println("s1.hashCode():"+s1.hashCode());
        System.out.println("s2.hashCode():"+s2.hashCode());
    }
}

class Singleton{
    private Singleton(){}//私有构造方法防止new

    private static class SingletonInstance{
        private static final Singleton INSTANCE=new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

分析:1)采用了类加载的机制保证线程安全;

           2)静态内部类方式使得在Singleton类被加载时不会立即实例化,而是在调用getInstance方法时实例化;

           3)类的静态属性只会在类第一次加载时初始化,所以在这里,JVM保证了线程安全性,在类进行初始化时,别的类是无法进入的;

           4)优点:线程安全,利用静态内部类实现延时加载,效率高

结论推荐使用

8.枚举

public class SingletonTest08 {
    public static void main(String[] args) {
        Singleton s1= Singleton.INSTANCE;
        Singleton s2= Singleton.INSTANCE;

        System.out.println(s1==s2);
        System.out.println("s1.hashCode():"+s1.hashCode());
        System.out.println("s2.hashCode():"+s2.hashCode());
    }
}

enum Singleton{
    INSTANCE;
}

分析:利用jdk1.5中添加的枚举实现单例模式,不仅能够避免多线程同步问题,而且还能防止反序列化重新创建新的对象;

结论推荐使用

 

我看到其他博友写6种实现方式的比较多,我这里把饿汉式细分成了静态变量和静态代码块两种实现方式,至于第5种实现方式,则是为了提醒大家这种实现方式并不能保证线程安全,原因和第3种实现方式相同,都是一个线程进入if后,还未执行下面的代码,假设有另一个线程抢占了CPU资源,开始执行代码,也通过了if语句,那么就会产生两个实例。

 

以上是我在观看了 尚硅谷Java设计模式 视频后,结合视频笔记以及自己理解整理出来的,如有错误或建议请多多指教。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>