单例模式主要解决的是,一个全局使用的类频繁的创建和消费。
日常场景:设置全局属性,数据库的连接池不会反复创建
主要实现有是否支持懒汉模式,是否线程安全。如果不需要考虑懒加载,可以直接使用static静态类或属性和方法来处理。
- 静态类使用
public class Singleton_00 {
public static Map<String, String> cache = new ConcurrentHashMap<>();
}
这样的静态类的方式可以再第一次运行的时候直接初始化Map类,同时这里也不需要到延迟加载再使用。在不需要维持任何状态下,仅仅用于全局访问,这样的方式比较方便。如果需要被继承以及维持一些特定状态,就适合使用单例模式。
- 懒汉模式(线程不安全)
public class Singleton_01 {
private static Singleton_01 instance;
private Singleton_01() {
}
public static Singleton_01 getInstance() {
if (instance != null) return instance;
instance = new Singleton_01();
return instance;
}
}
单例模式有一个特点,不允许外部直接创建,不能直接new Singleton_01(),所以这里的构造方法为private。
这种方式单例确实满足了懒加载,但如果有多个访问者同时去获取对象实例,就会造成多个同样的实例并存,从而没有达到单例的要求。
- 懒汉模式(线程安全)
public class Singleton_02 {
private static Singleton_02 instance;
private Singleton_02() {}
public static synchronized Singleton_02 getInstance() {
if (instance != null) return instance;
instance = new Singleton_02();
return instance;
}
}
这种方式是安全的,但由于把锁加到方法上后,所有的访问都因需要锁占用导致资源的浪费。
- 饿汉模式(线程安全)
public class Singleton_03 {
private static Singleton_03 instance = new Singleton_03();
private Singleton_03() {}
public static Singleton_03 getInstance() {
return instance;
}
}
这种与实例化Map一致,在程序启动的时候直接运行加载,后续有外部需要直接获取即可。但这种方式不是懒加载,就有种问题,你不需要的时候却直接实例化了,如果太大占用内存。
- 使用类的内部类(线程安全)
public class Singleton_04 {
private Singleton_04() {}
private static class SingletonHolder {
private static Singleton_04 instance = new Singleton_04();
}
public static Singleton_04 getInstance() {
return SingletonHolder.instance;
}
}
使用类的静态内部类实现的单例模式,既保证了线程安全又保证了懒加载,同时不会因为加锁导致消耗性能。这是因为JVM虚拟机可以保证多线程并发访问的正确性,一个类的构造方法在多线程环境下可以被正确的加载。这种方式实现单例模式非常推荐。
- 双重锁校验(线程安全)
public class Singleton_05 {
private static volatile Singleton_05 instance;
private Singleton_05() {}
public static Singleton_05 getInstance() {
if (instance != null) return instance;
synchronized (Singleton_05.class) {
if (instance == null) {
instance = new Singleton_05();
}
}
return instance;
}
}
双重锁的方式是方法级锁的优化,减少了部分获取实例的耗时,同时也满足了懒加载。
- CAS 线程安全)
public class Singleton_06 {
private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<>();
private Singleton_06() {}
public static final Singleton_06 getInstance() {
for (;;) {
Singleton_06 instance = INSTANCE.get();
if (instance != null) return instance;
INSTANCE.compareAndSet(null, new Singleton_06());
return INSTANCE.get();
}
}
public static void main(String[] args) {
System.out.println(Singleton_06.getInstance());
System.out.println(Singleton_06.getInstance());
}
}
java并发库提供了很多原子类来支持并发访问的数据安全性:AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference。
AtomicReference可以封装医用一个V实例,支持并发访问。使用CAS的好处就是不需要使用传统的枷锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发性。
但有个缺点就是忙等,如果一直没有获取到竟会处于死循环中。
- 枚举单列(线程安全)
public enum Singleton_07 {
INSTANCE;
public void test() {
System.out.println("hello");
}
public static void main(String[] args) {
Singleton_07.INSTANCE.test();
}
}
这种方式:线程安全、自由串行化、单一实例。
在继承场景下是不可用的。