signed

QiShunwang

“诚信为本、客户至上”

Java面试题集锦(19):写一个Singleton实例

2020/12/27 0:48:34   来源:

文章目录

      • 什么是Singleton?
      • 几种常见形式
        • 饿汉式
        • 懒汉式
      • Demo
        • 1. 饿汉式
          • (1)直接实例化饿汉式
          • (2)枚举式
          • (3)静态代码块饿汉式
        • 2. 懒汉式
          • (1)线程不安全的创建
          • (2)线程安全的创建
          • (3)静态内部类创建(线程安全)
      • 小结

插个眼:以后有机会写一篇专门讲设计模式的博客。

什么是Singleton?

Singleton:在Java中指单例设计模式,它是软甲开发中最常用的设计模式之一。(单:唯一,例:实例)

单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式
例如:代表JVM运行环境的Runtime类。

要点:

  1. 某个类只能由一个实例:构造器私有化
  2. 必须自行创建这个实例:含有一个该类的静态变量来保存这个唯一实例
  3. 必须自行向整个系统提供这个实例:对外提供或许该实例对象的方式(1.直接暴露、2.用静态变量的get方法)

几种常见形式

饿汉式

直接创建对象,不存在线程安全问题。

  1. 直接实例化饿汉式
  2. 枚举式
  3. 静态代码块饿汉式

懒汉式

延迟创建对象。

  1. 线程不安全(适用于单线程)
  2. 线程安全(适用于多线程)
  3. 静态内部类形式(适用于多线程)

Demo

1. 饿汉式

(1)直接实例化饿汉式
class Singleton {
    //为强调实例,用final修饰
    public static final Singleton INSTANCE = new Singleton();
    private Singleton() {
        
    }
}

获取方式:直接用【类名.实例】获取

public class Test {
    public static void main(String[] args) {
        Singleton s = Singleton.INSTANCE;
        System.out.println(s);
    }
}
(2)枚举式
/*
*枚举类型表示该类型的对象是有限的几个
* 我们限定为1个,就是单例
 */
public enum Singleton{
    INSTANCE
}
(3)静态代码块饿汉式

和(1)不同的是,(1)无法参入参数(可以传入写死的参数),在开发过程中,通过通过配置文件的方式传入参数,这时用(3)

class Singleton {
    public static final Singleton INSTANCE;
    public String info;
    static {
        try {
            //通过配置文件来导入参数信息
            Properties pro = new Properties();
            pro.load(Singleton.class.getClassLoader().getResourceAsStream("具体文件路径+名"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        INSTANCE = new Singleton(pro.getProperty("info"));
    }
    private Singleton(String info) {
        this.info = info;
    }
}

2. 懒汉式

(1)线程不安全的创建
class Singleton {
    private static Singleton instance;
    public Singleton(){}
    public static Singleton getInstance(){
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

获取方式:用【类名.get实例】获取

public class Scratch {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1);
        System.out.println(s1);
        System.out.println(s1==s2);

    }
}

在这里插入图片描述

输出结果显示,s1和s2是同一个对象。

这种方式是线程不安全的。当我用线程池创建了两个线程时,只要线程进入到getInstance方法发现是空的,就获取到一个新的instance,并发导致线程不安全。

import java.util.concurrent.*;

public class Scratch {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Singleton> c = new Callable<Singleton>() {
            @Override
            public Singleton call() throws Exception {
                return Singleton.getInstance();
            }
        };
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<Singleton> f1 = es.submit(c);
        Future<Singleton> f2 = es.submit(c);

        Singleton s1 = f1.get();
        Singleton s2 = f2.get();

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1==s2);

        es.shutdown();
    }

}

class Singleton {
    private static Singleton instance;
    public Singleton(){}
    public static Singleton getInstance(){
        if(instance == null) {
            try{
                Thread.sleep(100);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            instance = new Singleton();
        }
        return instance;
    }
}

在这里插入图片描述

(2)线程安全的创建

用==synchronized (Singleton.class){}==将Singleton.class锁住

class Singleton {
    private static Singleton instance;
    public Singleton(){}
    public static Singleton getInstance(){
        synchronized (Singleton.class){
            if(instance == null) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

再优化:加了synchronized之后导致并发性能降低,因为所有线程进入时都要等待解锁,其实如果instance不为空(已经被创建)的话,就没有必要等待其他线程解锁操作完成之后再判断instance是否为空了。
所以在最外层加一个if判断,这个判断不是为了解决线程安全问题(线程安全问题已由synchronized保证),而是为了提高并发性能。

class Singleton {
    private static Singleton instance;
    public Singleton(){}
    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null) {
                    try{
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
(3)静态内部类创建(线程安全)
//在内部类被加载和初始化时,才创建INSTANCE
//静态内部类不会自动随着外部类的加载和初始化而初始化
//它是要单独区加载和初始化的(在第一次使用的时候)
//这样保证了延迟创建
//因为是在内部类加载和初始化时 创建的 因此是线程安全的
class Singleton {
    private static Singleton instance;
    public Singleton(){}
    private static class Inner {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){
        return Inner.INSTANCE;
    }
}

小结

  • 如果是饿汉式,枚举形式最简单
  • 如果是懒汉式,静态内部类形式最简单