单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
业务中只需要一个实例对象,考虑用单例模式
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