单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
业务中只需要一个实例对象,考虑用单例模式
https://www.jb51.net/article/213888.htm#_label2
饿汉式
缺点: 类加载就初始化,浪费内存
优点: 没有加锁,执行效率高。还是线程安全的实例。
因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。单例就是该类只能返回一个实例。
换句话说,在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例。
也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。即饿汉式单例天生就是线程安全的。
public class Ehan{ private Ehan(){} //私有构造器 保证内存中只有一个对象 private static final Ehan ehan =new Ehan(); public static Ehan getInstance(){ return ehan; } }
懒汉式
用的时候再去加载,相对 节约了内存
存在线程安全问题
public class Lhan { private Lhan() { } private static Lhan lhan; public static Lhan getInstance() { if (lhan == null) { lhan = new Lhan(); } return lhan; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { Lhan.getInstance(); }).start(); } } }
实例在调用 getInstance
才会创建实例,这样的优点是不占内存,在单线程模式下,是安全的。但是多线程模式下,多个线程同时执行 if (ehSingleton == null)
结果都为 true
,会创建多个实例,所以上面的懒汉单例是一个线程不安全的实例。
双检锁
public class Lhan { private Lhan() { System.out.println(Thread.currentThread().getName() + " " + "ok"); } private static Lhan lhan; public static Lhan getInstance() { if(lhan==null){ //第一层是为了提高效率,减少线程对同步锁的竞争 。只有if ==true 才会获取锁 synchronized (Lhan.class) { if (lhan == null) { //第二层是为了单例 lhan = new Lhan(); } } } return lhan; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { Lhan.getInstance(); }).start(); } } }
Thread-1 ok 只执行一次 只有一个实例
public static Lhan getInstance() { if (lhan == null) { synchronized (Lhan.class) { if (lhan == null) { lhan = new Lhan();//不是原子性操作
1 分配空间
2 执行构造方法 初始化对象
3 对象指向空间
线程A 132
第3步时线程B来了,B认为lhan!=null ,就会直接返回,但是此时 lhan没有初始化
所以可能出现指令重排优化 需要用volatile
}
}
}
return lhan;
}
因此
这种最好
public class Lhan { private Lhan() { System.out.println(Thread.currentThread().getName() + " " + "ok"); } private static volatile Lhan lhan; //volatile 为了防止指令重排优化 public static Lhan getInstance() { if(lhan==null) { synchronized (Lhan.class) { if (lhan == null) { lhan = new Lhan(); } } } return lhan; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { Lhan.getInstance(); }).start(); } } }
内存占用率高,效率高,线程安全
但是依然可以用反射来破坏单例模式
静态内部类
静态内部类为什么是线程安全
静态内部类利用了类加载机制的初始化阶段 方法,静态内部类的静态变量赋值操作,实际就是一个 方法,当执行 getInstance() 方法时,虚拟机才会加载 SingletonHolder 静态内部类,
然后在加载静态内部类,该内部类有静态变量,JVM会改内部生成方法,然后在初始化执行方法 —— 即执行静态变量的赋值动作。
虚拟机会保证 方法在多线程环境下使用加锁同步,只会执行一次 方法。
这种方式不仅实现延迟加载,也保障线程安全。
public class Holder { private Holder() { System.out.println(Thread.currentThread().getName() + " " + "ok"); } public static Holder getInstance() { return Innerclass.holder; } public static class Innerclass { private static final Holder holder = new Holder(); } public static void main(String[] args) { for (int i = 0; i < 4; i++) { new Thread(() -> { Holder.getInstance(); }).start(); } } }
枚举
enum 本身也是一个class 类
public enum EnumSingle { INSTANCE; public EnumSingle getInstance() { return INSTANCE; } } class Test { public static void main(String[] args) { EnumSingle instance1 = EnumSingle.INSTANCE; EnumSingle instance2 = EnumSingle.INSTANCE; System.out.println(instance1); System.out.println(instance2); } }
INSTANCE
INSTANCE
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
2020-03-26 pycharm 运行变得很慢,怎么办??
2020-03-26 tf.reset_default_graph()