单例模式是Java中常用的设计模式之一,从系统启动到系统终止,保证一个类仅有一个实例,并提供一个访问它的全局访问点。例如spring中的bean默认都是单例模式。
单例模式的实现方式有多种,一般以下几种
- 饿汉式-变量初始化,饿汉式-静态块初始
- 懒汉式-同步锁,懒汉式-双重检查锁,懒汉式-内部类延时加载
- 登记注册式
饿汉式
饿汉式是保证线程安全的单例模式实现,在类加载的时候就进行单例对象初始化,典型的实现方式如下两种。
饿汉式-变量初始化
单例代码如下
public class HungerSingleton {
private static HungerSingleton singleton = new HungerSingleton();
private HungerSingleton(){
}
public static HungerSingleton getInstance(){
return singleton;
}
}
测试代码如下。
public class SingletonTest {
public static void main(String[] args) {
for (int i=0;i< 30;i++){
new Thread(() -> {
System.out.println(HungerSingleton.getInstance());
}).start();
}
}
}
可以测出多个线程获取的对象都是同一个。
饿汉式-静态块初始化
又或者是使用静态代码块初始化的方式,代码如下。
public class StaticHungerSingleton {
private static StaticHungerSingleton singleton;
static {
singleton = new StaticHungerSingleton();
}
private StaticHungerSingleton(){
}
public static StaticHungerSingleton getInstance(){
return singleton;
}
}
使用静态代码块的方式初始化单例值,也是线程安全的。
懒汉式
懒汉式单例会存在线程安全问题,导致单例获取的对象不一定唯一,所以一般需要做同步控制。
懒汉式-同步锁实现
public class SyncLazySingleton {
private static SyncLazySingleton singleton;
private SyncLazySingleton(){
}
public synchronized static SyncLazySingleton getInstance(){
if (singleton == null){
singleton = new SyncLazySingleton();
return singleton;
}else {
return singleton;
}
}
}
使用同步关键字,保证不会因为并发导致创建两个不同的对象。使用synchronized关键字实现,可以保证线程安全,但是在并发量大的情况下可能效率收到影响,因此可以做进一步的优化,使用双重检查锁方式优化实现。
懒汉式-双重检查锁实现
public class DubbleCheckLazySingleton {
private static volatile DubbleCheckLazySingleton singleton;
private DubbleCheckLazySingleton(){
}
public static DubbleCheckLazySingleton getInstance(){
if (singleton == null){
synchronized (DubbleCheckLazySingleton.class){
if (singleton == null){
singleton = new DubbleCheckLazySingleton();
return singleton;
}else {
return singleton;
}
}
}else {
return singleton;
}
}
}
代码如上所示,实现了更加细粒度的同步控制,因此效率比单独的同步互斥实现要好一点。
懒汉式-内部类延时加载实现
利用内部类延时加载机制以及类加载的安全机制,保证单例在初始时是线程安全的。
代码实现如下。
public class InnerClassLazySingleton {
private InnerClassLazySingleton(){
}
public static InnerClassLazySingleton getInstance(){
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder{
static InnerClassLazySingleton INSTANCE = new InnerClassLazySingleton();
}
}
上面 的实现方式既保证了懒汉式加载,也保证了线程安全。
登记注册式
登记注册式严格上来说并不是某一个类的单例的实现方式,而是多个类的单例维护的实现方式,例如spring中ioc容器,维护许多单例的bean。提供一个基于Map结构的容器,根据参数获取实例,对于某个参数固定返回一个实例,如果没有实例则进行初始化实例放入容器,然后返回实例。这里同样的,对于懒汉式加载,需要保证线程安全。下面看一个例子。
public class RegisterSingleton {
private static Map<String, Object> container = new HashMap<>();
private RegisterSingleton(){
}
public static Object getInstance(String type){
if (Objects.isNull(type)){
return null;
}
Object o = container.get(type);
if (Objects.isNull(o)){
doInitObject(type);
}
return container.get(type);
}
/**
* 保证初始化过程线程安全即可
* @param type
*/
private synchronized static void doInitObject(String type) {
if ("set".equals(type)){
container.put(type, new HashSet<>());
}else if ("list".equals(type)){
container.put(type, new ArrayList<>());
}
}
}
注册登记式更像是一种综合使用的方式,上面的保证初始化过程线程安全的实现方式可以多种,参考懒汉式加载的方式。最终实现是保证每次传相同的参数获取的对象都是同一个。
注意:序列化和反序列化导致单例不唯一
需要知道,对象实现了序列化接口之后是可以进行序列化的反序列化的,一个单例序列化之后,如果不进行重写readResolve方法,那么在反序列化时就会新建一个对象,导致单例不唯一。
public class SeriableHungerSingleton implements Serializable{
private static SeriableHungerSingleton singleton = new SeriableHungerSingleton();
private SeriableHungerSingleton(){
}
public static SeriableHungerSingleton getInstance(){
return singleton;
}
}
测试代码
public class SeriableTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
SeriableHungerSingleton singleton = SeriableHungerSingleton.getInstance();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("tmp.obj")));
objectOutputStream.writeObject(singleton);
objectOutputStream.flush();
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("tmp.obj")));
SeriableHungerSingleton singleton1 = (SeriableHungerSingleton) objectInputStream.readObject();
objectInputStream.close();
System.out.println(singleton);
System.out.println(singleton1);
}
}
输出结果
design.mode.singleton.seriable.SeriableHungerSingleton@7f31245a
design.mode.singleton.seriable.SeriableHungerSingleton@6d6f6e28
Process finished with exit code 0
经过重写readResolve方法,代码如下
public class SeriableHungerSingleton implements Serializable{
private static SeriableHungerSingleton singleton = new SeriableHungerSingleton();
private SeriableHungerSingleton(){
}
public static SeriableHungerSingleton getInstance(){
return singleton;
}
public Object readResolve(){
return singleton;
}
}
执行测试,输出结果
design.mode.singleton.seriable.SeriableHungerSingleton@7f31245a
design.mode.singleton.seriable.SeriableHungerSingleton@7f31245a
Process finished with exit code 0
保证了单例模式。