单例模式的作用
主要解决一个全局使用的类频繁的创建和消费,从而提升代码的整体性能。
单例模式的运用场景
1.数据库的连接池不会被反复创建;
2.spring中一个单例模式bean的生成和使用;
3.代码中需要设置全局的一些属性保存;
七种单例模式的实现
1.静态类的使用
public calss Singleton {
public static Map<String, String> cache = new ConcurrentHashMap<>();
}
1.这样静态类的方法可以在第一次运行的时候直接初始化Map,不需要到延迟加载再使用;
2.在不需要维持任何状态下,仅仅用于全局访问,这样使用静态类的方式更加方便;
3.如果需要被继承以及需要维持一些特定的状态,适合使用单例模式;
2.懒汉模式(线程不安全)
public calss Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if(null != instance) {
return instance;
}
intance = new Singleton();
return instance;
}
}
1.单例模式不允许外部直接创建,也就是new Singleton();所以默认的构造函数添加了私有属性private;
2.此方式的单例模式满足了懒加载,但如果多个访问者同时去获取对象实例,就会造成多个同样的实例并存,从而没有达到单例模式的要求;
3.懒汉模式(线程安全)
public calss Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if(null != instance) {
return instance;
}
instance = new Singleton();
return instance;
}
}
此种方式虽然是安全的,但由于锁加在方法上,所有的访问都需要锁占用导致资源的浪费,如果不是特定情况下,不建议此方式实现单例模式;
4.饿汉模式(线程安全)
public calss Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
1.此方式在程序启动的时候直接加载,外部需要使用的时候直接获取即可;
2.并不是懒加载,也就是不管你使用不使用,在程序启动之初都会创建;
3.那么这种⽅式导致的问题就像你下载个游戏软件,可能你游戏地图还没有打开呢,但是程序已经将
这些地图全部实例化。到你⼿机上最明显体验就⼀开游戏内存满了,⼿机卡了,需要换了。
5.使用类的内部类(线程安全)
public calss Singleton {
private static calss SingletonHolder {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
1.使用类的静态内部类实现的单例模式,既保证了线程安全又保证了懒加载,同时不会因为加锁的方式消耗性能;
2.因为java虚拟机可以访问多线程并保证访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确的加载;
3.十分推荐使用此种方法实现单例模式;
6.双重锁校验(线程安全)
public calss Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if(null != instance) {
return instance;
}
synchronized(Singleton.calss) {
if(null == instance) {
instance = new Singleton();
}
}
return instance;
}
}
1.双重锁的方式时方法锁的优化,减少了部分获取实例的耗时;
2.该方式满足懒加载;
7.CAS「AtomicReference」(线程安全)
public calss Singleton {
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();
private static Singleton instance;
private Singleton() {
}
public static final Singleton getInstance() {
for(; ;) {
Singleton instance = INSTANCE.get;
if(null != instance) {
return instance;
}
INSTANCE.compareAndSet(null, new Singleton());
return INSTANCE.get;
}
}
public static void main(String[] args) {
System.out.println(Singleton.getInstance());
}
}
1.java并发库提供了许多原子类来支持并发访问的数据安全性:AtomicInteger 、 AtomicBoolean 、 AtomicLong 、 AtomicReference 。
2.AtomicReference 可以封装引⽤⼀个V实例,⽀持并发访问如上的单例⽅式就是使⽤了这样的⼀个
特点。
3.使⽤CAS的好处就是不需要使⽤传统的加锁⽅式保证线程安全,⽽是依赖于CAS的忙等算法,依赖
于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外
的开销,并且可以⽀持较⼤的并发性。
4.当然CAS也有⼀个缺点就是忙等,如果⼀直没有获取到将会处于死循环中。
8.Effective Java作者推荐的枚举单例(线程安全)
public enum Singleton {
INSTANCE;
public void test(){
System.out.println("hi~");
}
}
1.Effective Java 作者推荐使⽤枚举的⽅式解决单例模式,此种⽅式可能是平时最少⽤到的。
2.这种⽅式解决了最主要的;线程安全、⾃由串⾏化、单⼀实例。
调试方式
@Test
public void test() {
Singleton.INSTANCE.test();
}
这种写法在功能上与共有域⽅法相近,但是它更简洁,⽆偿地提供了串⾏化机制,绝对防⽌对此实例
化,即使是在⾯对复杂的串⾏化或者反射攻击的时候。虽然这种⽅法还没有⼴泛采⽤,但是单元素的枚
举类型已经成为实现Singleton的最佳⽅法。但也要知道此种⽅式在存在继承场景下是不可⽤的。
总结
1.虽然只是⼀个很平常的单例模式,但在各种的实现上真的可以看到java的基本功的体现,这⾥包括了;懒汉、饿汉、线程是否安全、静态类、内部类、加锁、串⾏化等等。
2.在平时的开发中如果可以确保此类是全局可⽤不需要做懒加载,那么直接创建并给外部调⽤即可。
但如果是很多的类,有些需要在⽤户触发⼀定的条件后(游戏关卡)才显示,那么⼀定要⽤懒加载。
线程的安全上可以按需选择。