JavaSE面试题——Singleton单例模式的几种写法

简介: JavaSE面试题——Singleton单例模式的几种写法

1.Go!!!


我们都知道,在23设计模式中,单例模式是最容易理解、最简单的一种了,它也是软件开发中最常用的设计模式之一了。


单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。


主要有以下几个要点:

1.    这个类只能有一个实例对象:构造函数私有化。

2.    必须自行创建这个实例:含有一个该类的静态变量来保存这个唯一的实例。

3.    必须自行向整个系统提供这个实例:直接暴露(new)或者提供实例对象的get方法。

那么这篇文章我来分享一下单例模式的几种常见写法。

2.饿汉式


饿汉式。顾名思义,就是很饿,要吃东西(吃掉这个实例对象),所以不管三七二十一,先把实例对象给new出来。


2.1 直接实例化方式(简洁直观)

/**
 * 饿汉式:在类初始化时,直接创建实例对象,不管你是否需要这个对象
 *
 * (1) 构造函数私有化
 * (2) 自行创建, 并且用静态变量保存
 * (3) 强调这是一个单例,使用final修饰
 * (4) 向外提供这个实例
 */
public class Singleton01 {
    public static final Singleton01 INSTANCE = new Singleton01();
    private Singleton01() {}
}
/**
 *
 */
public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton01 s = Singleton01.INSTANCE;
        System.out.println(s);
    }
}


2.2 枚举方式(最简洁)

/**
 * 枚举类型:表示该类型的对象是有限的几个
 * 我们可以限定为1个, 就成了单例模式
 */
public enum Singleton02 {
    INSTANCE
}
/**
 *
 */
public class SingletonTest02 {
    public static void main(String[] args) {
        Singleton02 s = Singleton02.INSTANCE;
        System.out.println(s);
    }
}


2.3 静态代码块方式(适合复杂实例化)

/**
 * 静态代码块饿汉式(适合复杂实例化)
 */
public class Singleton03 {
    public static final Singleton03 INSTANCE;
    static {
        INSTANCE = new Singleton03();
    }
    private Singleton03() {}
}
/**
 *
 */
public class SingletonTest03 {
    public static void main(String[] args) {
        Singleton03 s = Singleton03.INSTANCE;
        System.out.println(s);
    }
}

3.懒汉式


懒汉式。同样顾名思义,就是很懒,但不至于说很饿,这种方式不会先new对象,而是表现的特别懒,等用到的时候我再new。(延迟创建对象的时机)


3.1 线程不安全方式(适合单线程)

/**
 * 懒汉式:延迟创建这个类的实例对象
 *
 * (1) 构造函数私有化
 * (2) 用一个静态变量保存这个唯一的实例
 * (3) 提供一个静态方法, 获取这个实例对象
 */
public class Singleton04 {
    private static Singleton04 instance;
    private Singleton04() {}
    public static Singleton04 getInstance() {
        if (instance == null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new Singleton04();
        }
        return instance;
    }
}
import java.util.concurrent.*;
/**
 *
 */
public class SingletonTest04 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Singleton04> callable = new Callable<Singleton04>() {
            @Override
            public Singleton04 call() throws Exception {
                return Singleton04.getInstance();
            }
        };
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<Singleton04> f1 = es.submit(callable);
        Future<Singleton04> f2 = es.submit(callable);
        Singleton04 s1 = f1.get();
        Singleton04 s2 = f2.get();
        System.out.println(s1 == s2);
        System.out.println(s1);
        System.out.println(s2);
        es.shutdown();
    }
}

运行上面的测试代码,可以看到在两个线程同时new实例对象时,就因为线程安全问题new了两个实例对象(Singleton04并未重写hashCodeequals方法,所以 == 比较的是对象的内存地址,false表明这是两个不同的对象)


3.2 线程安全synchronized方式(适合多线程)

/**
 * 懒汉式:延迟创建这个类的实例对象
 *
 * (1) 构造函数私有化
 * (2) 用一个静态变量保存这个唯一的实例
 * (3) 提供一个静态方法, 获取这个实例对象
 */
public class Singleton05 {
    private static Singleton05 instance;
    private Singleton05() {}
    public static Singleton05 getInstance() {
        if (instance == null) {
            synchronized (Singleton05.class) {
                if (instance == null) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Singleton05();
                }
            }
        }
        return instance;
    }
}

当两个线程同时到来,第一个线程先来getInstance,走到 synchronized 上锁(第二个线程等待),然后睡眠1000ms,让出cpu执行权。第二个线程得到执行权,但是代码块还被第一个线程锁着呢,所以第二个线程进不来。这个时候,第一个线程醒了,进而new了一次实例对象。此时第二个线程判断instance对象是否为null,因为之前已经被第一个线程new过一次了,所以这里instance不为null,直接返回即可。确保了自始至终只new了一次实例对象。


有关synchronized代码块外层的这个 if (instance == null) 是这样的,有这么一种情况:假如不加外层的这个if,那么第一个线程来到 getInstance 方法,上锁执行,进一步完成第一次new实例对象,而这个时候后面的多个线程纷纷到来,他们仍然需要去等待前面的线程依次释放类锁才可以进去判断instance对象是否为空,为空了new 不为空返回,这样效率会偏低。


这就有了一种优化策略,也就是在 synchronized代码块外层再加上一个判断instance是否为空的if,只要有一个线程抢到cpu执行权进去new了一次实例对象,那么后面的线程来到 getInstance 方法,走到第一个if,一看instance对象已经被new过了,直接返回就行了,不需要再傻傻的等待其他线程去释放类锁才可以执行自己的过程了。

import java.util.concurrent.*;
/**
 *
 */
public class SingletonTest05 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Singleton05> callable = new Callable<Singleton05>() {
            @Override
            public Singleton05 call() throws Exception {
                return Singleton05.getInstance();
            }
        };
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<Singleton05> f1 = es.submit(callable);
        Future<Singleton05> f2 = es.submit(callable);
        Singleton05 s1 = f1.get();
        Singleton05 s2 = f2.get();
        System.out.println(s1 == s2);
        System.out.println(s1);
        System.out.println(s2);
        es.shutdown();
    }
}


3.3 静态内部类方式(适合多线程)

/**
 * 在内部类被加载和初始化时,才会创建INSTANCE实例对象
 * 静态内部类不会自动随着外部类的加载和初始化而初始化,它是单独去加载和初始化的
 * 因为是在静态内部类加载和初始化时创建的实例对象,你不调用静态内部类就不会创建实例对象
 * 所以这是线程安全的
 */
public class Singleton06 {
    private Singleton06() {}
    private static class Inner {
        private static final Singleton06 INSTANCE = new Singleton06();
    }
    public static Singleton06 getInstance() {
        return Inner.INSTANCE;
    }
}
相关文章
|
8月前
|
存储 安全 Java
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day03】——JavaSE
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day03】——JavaSE
73 0
|
8月前
|
安全 Java 大数据
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day01】——JavaSE
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day01】——JavaSE
80 0
|
8天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
6月前
|
SQL 安全 Java
Android经典面试题之Kotlin中object关键字实现的是什么类型的单例模式?原理是什么?怎么实现双重检验锁单例模式?
Kotlin 单例模式概览 在 Kotlin 中,`object` 关键字轻松实现单例,提供线程安全的“饿汉式”单例。例如: 要延迟初始化,可使用 `companion object` 和 `lazy` 委托: 对于参数化的线程安全单例,结合 `@Volatile` 和 `synchronized`
71 6
|
6月前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
93 1
|
6月前
|
设计模式 安全 Java
Java面试题:什么是单例模式?如何在Java中实现单例模式?
Java面试题:什么是单例模式?如何在Java中实现单例模式?
75 0
|
6月前
|
设计模式 安全 Java
Java面试题:解释单例模式的实现方式及其优缺点,讨论线程安全性的实现。
Java面试题:解释单例模式的实现方式及其优缺点,讨论线程安全性的实现。
39 0
|
6月前
|
设计模式 安全 NoSQL
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
76 0
|
6月前
|
设计模式 安全 Java
Java面试题:如何实现一个线程安全的单例模式,并确保其在高并发环境下的内存管理效率?如何使用CyclicBarrier来实现一个多阶段的数据处理任务,确保所有阶段的数据一致性?
Java面试题:如何实现一个线程安全的单例模式,并确保其在高并发环境下的内存管理效率?如何使用CyclicBarrier来实现一个多阶段的数据处理任务,确保所有阶段的数据一致性?
81 0
|
6月前
|
存储 设计模式 监控
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
54 0

热门文章

最新文章