Bootstrap

java单例模式

目录

概念

饿汉模式

懒汉模式(单线程)

多线程单例模式的修改

多线程下饿汉模式

多线程下懒汉模式

加锁条件

加锁位置

总结


概念

单例模式是一种设计模式,按照设计模式来写代码,可以使代码不会太差(保证代码的下限),单例模式是指在一个进程中,限制某个类只能有唯一实例,在java中,单例模式有很多写法,我介绍了两种常见写法。

饿汉模式

public class Singletion {
    private static Singletion instance = new Singletion();
    public static Singletion getInstance() {
        return instance;
    }
}

饿汉模式的核心在于对类进行加载时,对类成员进行初始化,此处由static修饰的instance成员变量就是类成员,可以直接通过get方法拿到实例对象,这样避免了程序员自己去创建实例,保证了只有一个实例对象。

 新建一个主类,来测试是否为唯一实例:

public class dome1 {
    public static void main(String[] args) {
        Singletion singletion = Singletion.getInstance();
        System.out.println(singletion);
        Singletion singletion1 = Singletion.getInstance();
        System.out.println(singletion1);
    }
}

 在IDEA上运行程序,查看结果为:

可以看到通过get方法拿到的对象地址相同,为同一个对象。但是当我们new一个新的对象出来,又违背了只能有一个实例的初衷:

 可以看到new出来的对象是不同的,我们可以通过使用私有的构造方法,使其他类不能实例化对象,达到只能使用get方法来获取对象,在我创建的单例模式中加入私有的构造方法:

public class Singletion {
    private static Singletion instance = new Singletion();
    public static Singletion getInstance() {
        return instance;
    }
    private Singletion() {
    }
}

这是观察到其他类已经无法实例化对象了:

懒汉模式(单线程)

public class SingletonLazy {
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if(instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy(){

    }
}

懒汉模式的核心:在第一次使用时创建实例,提高了效率。在使用get方法时,首先判断instance对象是否为空,为空就创建实例,不为空直接返回。测试是否为同一实例:

 可以看到得到的结果是同一个实例。

多线程单例模式的修改

多线程下饿汉模式

首先,饿汉模式在多线程情况下是安全的,多个线程同时调用get方法,使用的都是同一个实例,不存在示例的创建和修改。创建多个线程,同时使用get方法拿到实例。

public class Dome3 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
           Singletion singletion = Singletion.getInstance();
            System.out.println(singletion);
        });
        Thread t2 = new Thread(()->{
            Singletion singletion = Singletion.getInstance();
            System.out.println(singletion);
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                Singletion singletion = Singletion.getInstance();
                System.out.println(singletion);
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

观察结果:

 此时可以发现,三个线程拿到的都是同一个实例。

多线程下懒汉模式

在多个线程同时使用懒汉模式的实例时,会出现线程不安全情况,仍然创建多个线程来拿取懒汉模式下创建的唯一单例:

public class Dome4 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            SingletonLazy singletonLazy = SingletonLazy.getInstance();
            System.out.println("t1拿到的实例"+singletonLazy);
        });
        Thread t2 = new Thread(()->{
            SingletonLazy singletonLazy = SingletonLazy.getInstance();
            System.out.println("t2拿到的实例"+singletonLazy);
        });
        Thread t3 = new Thread(()->{
            SingletonLazy singletonLazy = SingletonLazy.getInstance();
            System.out.println("t3拿到的实例"+singletonLazy);
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

观察结果;

此时可以看见t2和t3线程拿到的实例和t1不是相同的实例,在多线程的环境下,上面写的懒汉模式明显没有做到只有唯一实例,下面对单线程情况下的懒汉模式进行分析:

懒汉模式是在程序运行时,先判断是否已经存在唯一实例,没有存在就创建实例,这种判断在多线程中是不安全的。当两个线程同时拿到空实例时,便会创建出两个实例,此时,便打破了单例模式的规则。

这时,加锁就可以解决这个问题:

加锁条件

首先需要判断加锁的条件,试想一下,如果实例已经创建,仍然进行加锁操作,那么就是白白浪费资源,可以先判断一下唯一实例是否被创建,在进行加锁操作。使用if语句判断唯一实例是否为空;

 if(instance == null) {
            //进行加锁操作
        }

加锁位置

锁的位置应该是放在出现线程安全问题的地方,是由于if判断和实例化对象(new操作)之间出现线程切换导致线程安全问题,所以应该通过锁把if判断和new操作打包成一个整体来保证线程的安全。

public class SingletonLazy {
    private static SingletonLazy instance = null;
    private static Object object = new Object();
    public static SingletonLazy getInstance() {
        if(instance == null) {
            //进行加锁操作
            synchronized (object) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        if(instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy(){

    }
}

此时,在此运行之前的测试方法,查看结果:

观察结果,此时三个线程拿到的实例相同,懒汉模式线程不安全的问题也得到了解决。最后,为了解决指令重排序的问题,对唯一实例加上volatile,确保多线程情况下更安全。

总结

单例模式的前提是在一个进程中,如果是多个java进程,自然每个进程都可以有一个实例。

单例模式只能避免失误,但不能避免恶意攻击(反射,序列化反序列化)。

关于单例模式的介绍就到这里,单例模式的设计有很多,我的文章中具体展示了两种,并不代表只有两种。 

;