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() {@Overridepublic 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(){}
}
此时,在此运行之前的测试方法,查看结果:
观察结果,此时三个线程拿到的实例相同,懒汉模式线程不安全的问题也得到了解决。
总结
单例模式的前提是在一个进程中,如果是多个java进程,自然每个进程都可以有一个实例。
单例模式只能避免失误,但不能避免恶意攻击(反射,序列化反序列化)。
关于单例模式的介绍就到这里,单例模式的设计有很多,我的文章中具体展示了两种,并不代表只有两种。