回字有四种写法,那你知道单例有五种写法吗

单例模式(Singleton)应该是大家接触的第一个设计模式,其写法相较于其他的设计模式来说并不复杂,核心理念也非常简单:程序从始至终只有同一个该类的实例对象。

举一个耳熟能详的例子,比如LOL中的大龙,一场游戏下来无论如何只有一只,所以该类只能被实例化一次。再举一个我们应用程序开发中常见的例子,Spring框架中的Bean作用范围默认也是单例的。

我相信大家都知道单例的两种最基本的写法:饿汗式和懒汉式。但是这两种写法都有其弊端所在,除了这两种写法外其实还有几种写法。此时耳边仿佛听到孔乙己的声音:

“对呀对呀!......回字有四样写法,你知道么?”。

我愈不耐烦了,努着嘴走远。孔乙己刚用指甲蘸了酒,想在柜上写字,见我毫不热心,便又叹一口气,显出极惋惜的样子........

大家先别着急走,回字的四样写法没必要知道,单例的五种写法还是有必要晓得滴,其他的不说,至少面试的时候还能和面试官吹下是不,况且这几种写法也不是纯吊书袋,了解过后还是能帮助我们理解其设计思想滴。所以接下来咱们由浅入深,从最容易的写法开始,一步一步的带大家掌握单例模式!

写法介绍 饿汉式

话不多说,先直接上最简单的写法,然后咱再慢慢剖析:

public class Signleton01 { // 私有构造函数,防止别人实例化 private Signleton01(){} // 静态属性,指向一个实例化对象 private static final Signleton01 INSTANCE = new Signleton01(); // 公共方法,以便别人获取到实例化对象属性 public static Signleton01 getINSTANCE() { return INSTANCE; } } 单例模式三元素

一个单例模式就这样写完了,简直不要太简单。 类里面一共就三个元素:

私有构造函数,防止别人实例化

静态属性,指向一个实例化对象

公共方法,以便别人获取到实例化对象属性

这三个元素就是单例模式的核心,单例无论哪种写法,都离不开这三个元素

这三个元素也很好理解,别人想要用我这个类的实例对象就只能通过我提供的getINSTANCE(),他想new也new不了第二个对象,自然而然就保证了该类只有唯一对象。我们可以做个试验,跑100个线程同时获取该类的实例对象,然后打印出对象的hashCode,看看到底是不是获取的同一个对象:

public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(() -> { System.out.println(Signleton01.getINSTANCE().hashCode()); }).start(); } }

结果如下:

... 834649078 834649078 834649078 834649078 834649078 ...

嗯,全部都是同一个对象。

优缺点

优点:写法简单,线程安全

缺点:消耗资源,即使程序从没有用到过该类对象,该类也会初始化一个对象出来

所以为了解决饿汗式的这个缺点, 我们就引出了第二种写法,懒汉式!

懒汉式 基本写法 public class Singleton02 { // 私有构造函数,防止别人实例化 private Singleton02() {} // 静态属性,指向一个实例化对象(注意,这里没有实例化对象哦) private static Singleton02 INSTANCE; // 公共方法,以便别人获取到实例化对象属性 public static Singleton02 getINSTANCE() { if (INSTANCE == null) { INSTANCE = new Singleton02(); } return INSTANCE; } }

懒汉式的和饿汗式最大的区别是什么呢,就是只有在调用getINSTANCE的时候,才会创建实例,如果你从来没调用过,那么就不实例化对象。这个就比饿汗式更加节约资源,不过这种写法并不是懒汉式的完善写法,它有一个非常大的问题,就是线程不同步!我们可以按照之前那种方式创建100个线程测试一下结果:

... 1851261656 868907500 988762476 1031371881 593800070 ...

可以看到这线程一同时拿,拿的都不是同一个对象,这完全就破坏了单例模式。因为很多线程在对象没有初始化前就进入到了if (INSTANCE == null) 判断语句块里,自然而然就会new出不同的对象了。要解决这个线程不安全问题,就得上线程锁!

synchronized写法 public class Singleton02 { private Singleton02() {} private static Singleton02 INSTANCE; // 注意,这里静态方法加了synchronized关键字 public synchronized static Singleton02 getINSTANCE() { if (INSTANCE == null) { INSTANCE = new Singleton02(); } return INSTANCE; } }

当我们在静态方法加上synchronized关键字后,就可以保证这个方法在同一时间只会有一个线程能成功调用,也就顺理成章的解决了线程不安全问题。我们还是测试一下:

... 1226880356 1226880356 1226880356 1226880356 1226880356 ...

不管多少个线程,拿到的都是同一个对象,达到了单例的要求!

优缺点

懒汉式连基本的线程安全都不能保证,就不做讨论了,我们这里主要说的事synchronized写法

优点:写法简单,节约资源(只有需要该对象的时候才会实例化)

缺点:耗性能

要知道每一次调用getINSTANCE()方法时都会上锁,这是非常耗性能的。那么为了解决这个好性能的问题,我们又引申出接下来的一种写法。

双重检测

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwfsjd.html