我给面试官讲解了单例模式后,他对我竖起了大拇指!

r囧r小猫 2023-09-26 09:30 181阅读 0赞

目录

一、单例模式定义:

二、单例模式介绍:

三、单例模式注意事项和细节说明:

四、单例模式有八种方式:

五、单例模式的八种实现方式:

1.饿汉式(静态变量):线程安全

2.饿汉式(静态代码块):线程安全

3.懒汉式:(线程不安全)

4.懒汉式:(线程安全,同步方法)

5.懒汉式(线程安全,同步代码块)

6.双检锁 / 双重检查(DCL,即 double-checked locking):推荐使用

7.静态内部类(线程安全):推荐使用

8.枚举(线程安全):推荐使用

六、经验之谈:

一、单例模式定义:

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式。所谓类的单例设计模式,就是采取一定的方法保证整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

二、单例模式介绍:

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当你想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

应用实例: 1、一个党只能有一个主席。 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

使用场景: 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:getInstance () 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

三、单例模式注意事项和细节说明:

(1)单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建和销毁的对象,使用单例模式可以提高系统性能。

(2)当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法(如:getInstance()),而不是通过new实例化对象。

(3)单例模式使用的场景:需要频繁的进行创建和销毁的对象,创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

四、单例模式有八种方式:

橙色:可以使用 黑色:不可以使用 红色:推荐使用

(1)饿汉式(静态常量)

(2)饿汉式(静态代码块)

(3)懒汉式(线程不安全)

(4)懒汉式(线程安全,同步方法)

(5)懒汉式(线程安全,同步代码块)

(6)双检锁 / 双重检查(DCL,即 double-checked locking)

(7)静态内部类

(8)枚举

五、单例模式的八种实现方式:

如下代码测试两个实例对象是否一样:

  1. public class SingletonPaternDemo {
  2. public static void main(String[] args) {
  3. //测试
  4. Singleton instance1 = Singleton.getInstance();
  5. Singleton instance2 = Singleton.getInstance();
  6. System.out.println(instance1 == instance2 );//true
  7. System.out.println("instance1.hashCode="+instance1.hashCode());
  8. System.out.println("instance2.hashCode="+instance2.hashCode());
  9. }
  10. }

#

1.饿汉式(静态变量):线程安全

  1. class SingleObject {
  2. //1.构造函数私有化,外部不能通过new创建对象
  3. private SingleObject(){
  4. }
  5. //2.本类内部创建对象实例
  6. private final static SingleObject instance = new SingleObject();
  7. //3.提供一个共有的静态方法,返回实例对象
  8. public static SingleObject getInstance(){
  9. return instance;
  10. }
  11. }

#

2.饿汉式(静态代码块):线程安全

  1. public class Singleton {
  2. //1.构造器私有化,外部不能new
  3. private Singleton() {}
  4. //2.本类内部创建对象实例
  5. private static Singleton instance ;
  6. static{//在静态代码块中,创建单例对象
  7. instance = new Singleton();
  8. }
  9. public static Singleton getInstance() {
  10. return instance;
  11. }
  12. }

饿汉式优缺点说明:

(1)优点:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题,是线程安全的,同时因为没有加锁,执行效率会提高。

(2)缺点:在类加载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终未使用过这个实例,就会造成内存浪费。

(3)这种方式基于classOrder机制避免了多线程的同步问题,不过,instance在类加载时候实例化,在单例模式中大多数都是调用getInstance方法,但是导致类加载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类加载,这时候初始化instance就没有达到lazy loading的效果。

(4)结论:这种单例模式可以用,但如果自始至终没有用到这个实例会造成内存浪费。

#

3.懒汉式:(线程不安全)

  1. public class Singleton {
  2. private static Singleton instance;
  3. //静态构造方法,不允许外界new创建对象
  4. private Singleton() {}
  5. //提供一个静态的公有方法,当使用到该方法时,才去创建instance
  6. //即懒汉式(延迟创建)
  7. public static Singleton getInstance() {
  8. if (instance == null) {
  9. instance = new Singleton();
  10. }
  11. return instance;
  12. }
  13. }

优缺点说明:

(1)起到了Lazy Loading的效果,但是只能在单线程下使用。

(2)如果在多线程下,当一个线程进入了if(singleton == null)判断语句块中,还没有来得及往下执行时,此时另一个线程也通过了这个判断语句,这时就会产生多个实例。所以在多线程环境下不能使用这种方式。

(3)结论:在实际开发中,不要使用这种方式。

#

4.懒汉式:(线程安全,同步方法)

  1. public class Singleton {
  2. private static Singleton instance;
  3. //静态构造方法,不允许外界new创建对象
  4. private Singleton() {}
  5. //提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
  6. //即懒汉式(延迟创建)
  7. public static synchronized Singleton getInstance() {
  8. if (instance == null) {
  9. instance = new Singleton();
  10. }
  11. return instance;
  12. }
  13. }

优缺点说明:

(1)优点:解决了线程不安全问题。

(2)缺点:效率太低了,每个线程在想获得类的实例时,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获取该类实例时,直接return就行了。方法进行同步效率太低了。

(3)结论:在实际开发中,能使用但不推荐使用这种方式。

5.懒汉式(线程安全,同步代码块)

  1. public class Singleton {
  2. private static Singleton instance;
  3. //静态构造方法,不允许外界new创建对象
  4. private Singleton() {}
  5. //提供一个静态的公有方法,当使用到该方法时,才去创建instance
  6. //即懒汉式(延迟创建)
  7. public static Singleton getInstance() {
  8. if (instance == null) {
  9. synchronizedSingleton.class){
  10. instance = new Singleton();
  11. }
  12. }
  13. return instance;
  14. }
  15. }

优缺点说明:

(1)这种方式,本意是对第四种实现方式的改进,因为第四种同步方法效率太低,所以改为同步产生实例化的代码块。

(2)但是这种同步并不能起到线程同步的作用。跟第三种实现方式遇到的情形一致,假如一个线程进入了if(singleton == null)判断语句块中,还没有来得及往下执行,这时另一个线程也通过了这个判断语句,便会产生多个实例。

(3)结论:在实际开发中,不能使用这种方式。

6.双检锁 / 双重检查(DCL,即 double-checked locking):推荐使用

  1. //双重检查,推荐使用
  2. public class Singleton {
  3. private volatile static Singleton singleton;
  4. private Singleton() {}
  5. //提供一个静态的共有方法,加入双重检查代码,实现线程安全,同时达成懒加载功能
  6. public static Singleton getSingleton() {
  7. if (singleton == null) {
  8. synchronized (Singleton.class) {
  9. if (singleton == null) {
  10. singleton = new Singleton();
  11. }
  12. }
  13. }
  14. return singleton;
  15. }
  16. }

优缺点说明:

(1)Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次

if(singleton == null)检查,进入第二次检查之前用了同步方法,这样就可以保证线程安全了。

(2)这样一来,实例化代码只用执行一次,后面再次访问时,判断if(singleton == null),直接return 实例化对象,也避免了反复进行方法同步。

(3)优点:线程安全、延迟加载、效率较高

(4)结论:在实际开发中,推荐使用这种单例设计模式。

7.静态内部类(线程安全):推荐使用

  1. //静态内部类实现,推荐使用
  2. public class Singleton {
  3. private Singleton() {}
  4. //写一个静态内部类,该类中有一个静态属性Singleton
  5. private static class SingletonInstance {
  6. private static final Singleton INSTANCE = new Singleton();
  7. }
  8. //提供一个静态的共有方法,直接返回SingletonInstance.INSTANCE
  9. public static final Singleton getInstance() {
  10. return SingletonInstance.INSTANCE;
  11. }
  12. }

优缺点说明:

(1)这种方式采用了类装载的机制来保证初始化实例时只有一个线程。(因为类装载时只有一个线程
(2)静态内部类方式在Singleton类被加载时并不会立即实例化(因为静态内部类被调用时才会被加载),而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

(3)类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的

(4)优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。

(5)结论:在实际开发中,推荐使用。

8.枚举(线程安全):推荐使用

  1. public class SingletonPaternDemo {
  2. public static void main(String[] args) {
  3. //测试
  4. Singleton instance1 = Singleton.INSTANCE;
  5. Singleton instance2 = Singleton.INSTANCE;
  6. System.out.println(instance1 == instance2 );//true
  7. System.out.println("instance1.hashCode="+instance1.hashCode());
  8. System.out.println("instance2.hashCode="+instance2.hashCode());
  9. instance.sayOk();
  10. }
  11. }
  12. //使用枚举,可以实现单例
  13. enum Singleton{
  14. INSTANCE;//属性
  15. public void sayOk(){
  16. System.out.println("ok");
  17. }
  18. }

1.优缺点说明:

(1)这种方式借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

(2)这种方式是Effective Java作者Josh Bloch提倡的方式。

(3)结论:在实际开发中,推荐使用。

2.描述:

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用

六、经验之谈:

一般情况下,最好不要使用第 3种、第 4 种和第5种懒汉方式建议使用第 1种或第二种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 7种静态内部类。如果涉及到反序列化创建对象时,可以尝试使用第 8 种枚举方式。如果有其他特殊的需求,可以考虑使用第 6种双检锁方式

推荐一篇热榜单例模式:

我给面试官讲解了单例模式后,他对我竖起了大拇指!

发表评论

表情:
评论列表 (有 0 条评论,181人围观)

还没有评论,来说两句吧...

相关阅读