设计模式之单例模式
介绍
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应用实例:
- 1、一个班级只有一个班主任。
- 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
代码实现
饿汉式单例模式
实现了单例模式且线程安全,性能也不错,但如果该类初始化时占用巨大的内存资源的话,在其初始化就创建了对象会很占内存空间
public class SingleObject {
private static SingleObject instance = new SingleObject();
//构造函数私有
private SingleObject() {
System.out.println("线程创建成功");
}
//获取唯一对象
public static SingleObject getInstance() {
return instance;
}
public void PrintSomething(){
System.out.println("hello");
}
public static void main(String[] args) {
//饿汉式单例模式创建唯一对象
SingleObject so = SingleObject.getInstance();
SingleObject so2 = SingleObject.getInstance();
SingleObject so3 = SingleObject.getInstance();
System.out.println(so==so2);
System.out.println(so3==so2);//结果为true证明so so2 so3是同一个对象,实现了单例模式
//线程安全测试,在SingleObject的构造函数里打印输出语句,经测试只输出一次,证明线程安全
for (int i = 0; i < 10; i++) {
new Thread(()->{
SingleObject.getInstance();
}).start();
}
}
}
懒汉式设计模式(线程不安全)
线程不安全,即在多线程下未实现单例模式
public class SingletonTN {
private static SingletonTN instance;
private SingletonTN() {
System.out.println("线程安全测试");
}
public static SingletonTN getInstance() {
if (instance == null) {
instance = new SingletonTN();
}
return instance;
}
public static void main(String[] args) {
//线程安全测试,在SingletonTN的构造函数里打印输出语句,经测试不固定次数的打印了输出语句,证明创建了多个对象
//经测试证明其未实现多线程模式下的单例模式
for (int i = 0; i < 10; i++) {
new Thread(()->{
SingletonTN.getInstance();
}).start();
}
//单线程下实现了单例模式
SingletonTN sl = SingletonTN.getInstance();
SingletonTN s2 = SingletonTN.getInstance();
System.out.println(sl == s2);//结果为true证明s1 s2是同一个对象,实现了单例模式
}
}
懒汉式设计模式(线程安全 synchronized)
实现了单例模式且线程安全,但由于加锁,所以性能上不会很高
public class SingletonTY {
private static SingletonTY instance;
private SingletonTY() {
System.out.println("线程安全测试");
}
//加入线程同步关键字synchronized
public static synchronized SingletonTY getInstance() {
if (instance == null) {
instance = new SingletonTY();
}
return instance;
}
public static void main(String[] args) throws Exception {
//加入线程同步关键字synchronized后线程安全测试,在SingletonTY的构造函数里打印输出语句,经测试仅仅打印了输出语句一次,证明创建了一个对象
//实现了多线程单例模式
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletonTY.getInstance();
}).start();
}
//单线程下实现了单例模式
SingletonTY sl = SingletonTY.getInstance();
SingletonTY s2 = SingletonTY.getInstance();
System.out.println(sl == s2);//结果为true证明s1 s2是同一个对象,实现了单例模式
//利用反射破坏private私有构造函数从而破坏单例模式
Constructor<SingletonTY> constructor = SingletonTY.class.getDeclaredConstructor();
constructor.setAccessible(true);
SingletonTY c1 = constructor.newInstance();
SingletonTY c2 = constructor.newInstance();
System.out.println(c1==c2);
}
}
枚举实现单例模式
线程安全,性能高,简单,也不会被反射破坏单例模式,是很理想的单例模式,缺点为不能懒加载,在不是一定需要懒加载的情况下,使用该方法是最佳的单例实现形式
public enum SingleEnum {
INSTANCE;
private SingleEnum() {
System.out.println("枚举单例初始化");
}
public void test() {
System.out.println("hello");
}
public static void main(String[] args) {
//枚举单例测试,经过测试发现构造方法打印输出了一次,只会创建一次对象所以是单例模式
SingleEnum.INSTANCE.test();
SingleEnum.INSTANCE.test();
SingleEnum.INSTANCE.test();
SingleEnum.INSTANCE.test();
SingleEnum.INSTANCE.test();
//线程安全测试,经过测试构造方法打印输出了一次,只会创建一次对象所以是单例模式
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingleEnum.INSTANCE.test();
}).start();
}
}
}
如果明确需要懒加载,可以使用静态内部类的单例模式
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}