解释
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 单例类只能有一个实例。
-
单例类必须自己创建自己的唯一实例。
-
单例类必须给所有其他对象提供这一实例。
优点:
-
在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
-
避免对资源的多重占用(比如写文件操作)。
缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
实现:
UML图:
SingleObject:
public class SingleObject {
private static SingleObject instance = new SingleObject();
private SingleObject(){}
public static SingleObject getInstance() {
return instance;
}
public void sayHello(){
System.out.println("Hello World!");
}
}
Test:
public class Test {
public static void main(String[] args) {
//获取唯一可用的对象
SingleObject singleObject = SingleObject.getInstance();
// Hello World!
singleObject.sayHello();
}
}
常见5种实现方式
常见的单例模式实现方式有五种:饿汉式
、懒汉式
、双重检测锁式
、静态内部类式
和枚举单例
。而在这五种方式中饿汉式
和懒汉式
又最为常见。下面将一一列举这五种方式的实现方法:
1.饿汉式:
线程安全,调用效率高。但是不能延时加载。
public class SingletonDemo1 {
/**
* 线程安全的
* 类初始化时,立即加载这个对象
*/
private static SingletonDemo1 instance = new SingletonDemo1();
private SingletonDemo1(){}
/**
* 方法没有加同步块,所以它效率高
* @return
*/
public static SingletonDemo1 getInstance() {
return instance;
}
}
由于该模式在加载类的时候对象就已经创建了,所以加载类的速度比较慢,但是获取对象的速度比较快,且是线程安全的
2.懒汉式:
线程不安全的
public class SingletonDemo2 {
// 类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
private static SingletonDemo2 instance = null;
private SingletonDemo2(){}
public static SingletonDemo2 getInstance() {
if (instance == null) {
instance = new SingletonDemo2();
}
return instance;
}
}
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (instance \=\= null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
懒汉式的扩展:
线程安全,调用效率不高,但是能延时加载
public class SingletonDemo4 {
private static SingletonDemo4 instance = null;
private SingletonDemo4(){}
public static synchronized SingletonDemo4 getInstance() {
if(instance == null){
instance = new SingletonDemo4();
}
return instance;
}
}
在getInstance方法中添加了
synchronized
保证线程安全,缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。
3.双重同步锁:
线程安全;延迟加载;效率较高。
public class SingletonDemo3 {
private static volatile SingletonDemo3 instance = null;
private SingletonDemo3(){}
public static SingletonDemo3 getInstance() {
if (instance == null) {
synchronized (SingletonDemo3.class){
if (instance == null) {
instance = new SingletonDemo3();
}
}
}
return instance;
}
}
如代码中所示,我们进行了两次if (instance \=\= null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (instance \=\= null),直接return实例化对象。
4.静态内部类:
public class SingletonDemo5 {
private SingletonDemo5(){}
private static class SingletonInstance{
private static final SingletonDemo5 INSTANCE = new SingletonDemo5();
}
public static SingletonDemo5 getInstance(){
return SingletonInstance.INSTANCE;
}
}
静态内部类这种方式和饿汉式机制其实差不多,虽然两者都是采用了类装载的机制来保证初始化实例时只有一个线程,但同的地方在饿汉式方式是类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成实例化。
5.枚举类
线程安全,调用效率高,不能延时加载
public enum Singleton {
//枚举元素本身就是单例
INSTANCE;
//添加自己需要的操作
public void singletonOperation(){
}
}
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化