单例模式是java中最简单也是最重要的一种设计模式,面试的时候也会经常被问到。本篇文章来进行一个总结,提供给读者面试和工作使用。
对象创建模式,单例涉及到一个单一的类,确保只有单个对象被创建,这个单一类提供一种唯一访问对象的方式,不需要再实例化对应的类对象。
单例模式主要解决全局使用的类的频繁的创建和销毁的问题,当想控制实例数目,节省系统资源的时候考虑单例设计。其中的关键代码为构造函数是私有的。
优点 | 缺点 |
1.内存只有一个实例,减少内存开销,频繁的创建和销毁 | 1.没有接口,不能继承,扩展困难 |
2.避免资源的多重占用 | 2.与单一职责冲突,一个类应该只关心内部逻辑,不该关心外面的实例化。 |
代码:
public class LazySingle {
private static LazySingle instance;
private LazySingle() {
System.out.println("我是实例");
}
public static LazySingle getInstance() {
if (instance == null) {
instance = new LazySingle();
}
return instance;
}
}
这种线程不安全,多线程不能正常的工作,并发的时候没加锁。
代码:
public class LazySingle {
private static LazySingle instance;
private LazySingle() {
System.out.println("我是实例");
}
public static synchronized LazySingle getInstance() {
if (instance == null) {
instance = new LazySingle();
}
return instance;
}
public static void main(String[] args) {
System.out.println(LazySingle.getInstance());
}
}
相比上面而言,加上了锁synchronized,实现了线程安全,但是性能会降低,99%不需要同步。
饿汉式相比懒汉式是先初始化完实例,会浪费内存空间,但是因为没有加锁的关系,在线程安全的情况下效率高。
代码:
public class HungrySingle {
private static HungrySingle instance = new HungrySingle();
private HungrySingle() {
System.out.println("我是饿汉模式啊");
}
public static HungrySingle getInstance() {
return instance;
}
public static void main(String[] args) {
System.out.println(HungrySingle.getInstance());
}
}
这种的下面会重点进行介绍这里不再上代码,这种方式是比较推崇的一种设计,在多线程保证安全的情况下保持高性能。
代码:
public class InnerSingle {
private static class InnerSingleHolder {
private static final InnerSingle INSTANCE = new InnerSingle();
}
private InnerSingle() {
System.out.println("内部类的实现");
}
public static final InnerSingle getInstance() {
return InnerSingleHolder.INSTANCE;
}
public static void main(String[] args) {
System.out.println(InnerSingle.getInstance());
}
}
内部类的单例模式是利用了内部类只会初始化一次静态方法的原理实现的单例,线程安全,高效,但是试用场景就是静态域的业务场景下。
双锁机制下实现线程安全的高效单例模式:
上代码:
public class Singleton {
//1.volatile修饰
private volatile static Singleton singleton;
private Singleton() {
System.out.println("双重检查的单例模式");
}
public static Singleton getSingleton() {
if (singleton == null) {//2.第一次判断
synchronized (Singleton.class) {//3.开始加锁
if (singleton == null) {//4.第二次判断
singleton = new Singleton();//5.存在指令重排的可能
}
}
}
return singleton;//6.返回实例
}
public static void main(String[] args) {
System.out.println(Singleton.getSingleton());
}
}
这里提一下,就是解决并发的三个问题:
synchronizs:保证了原子性、可见性、线程的执行顺序
volatile:保证可见性、禁止指令重排
上面的volatile修饰变量,就是为了防止步骤5的指令重排。
位置5有三个动作:
synchronize只是保证了多线程的执行顺序,对于单个线程的内部指令的执行顺序,针对上面的三个动作,线程可能为1-2-3,也可能是1-3-2,当第二个线程拿到的实例对象还没初始化的时候就有可能报错。所以变量就需要volatile进行修饰,禁止指令重排,保证代码的执行正确性。
上面就是我关于java单例模式的总结,如果面试的时候你把上面我总结的东西形成自己的语言进行描述出来,那么肯定是没问题的,最重要的是形成自己的语言。如果想跟我有更多的交流,欢迎关注我的公众号:Java时间屋 进行交流。
页面更新:2024-05-19
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号