单例是确保一个类在系统中只有一个实例,提供该实例的全局访问点。
实现单例基本思想是使用私有构造函数保证无法被其他类new实例化,通过私有静态变量和一个获得实例的方法保证每次获取的实例都是同一个实例。
实现方式:
1、懒汉式-线程不安全
public class Signleton {
private static Signleton uniqueInstance;
public static Signleton getUniqueInstance() {
if (uniqueInstance == null) {//线程不安全
return new 懒汉式_线程不安全();
}
return uniqueInstance;
}
}
这种实现在单线程下是十分有效的,保证了在需要的时候才实例化对象,节省资源。
但是在多线程环境下,如果未实例化情况下多个线程走到判空语句,就会实例化多次,导致单例失败。
2、饿汉式-线程安全
public class Signleton {
private static Signleton uniqueInstance = new Signleton();
public static Signleton getUniqueInstance(){
return uniqueInstance;
}
}
这种初始化时直接实例化,虽然保证了线程安全,不过失去了延迟实例化的优势。
3、懒汉式-线程安全
public class Signleton {
private static Signleton uniqueInstance;
public static synchronized Signleton getUniqueInstance() {
if (uniqueInstance == null) {
return new Signleton();
}
return uniqueInstance;
}
}
相比于上一个代码,在获取实例方法上加了synchronized关键字后,保证同一时间只有一个线程进入到方法中,但是如果多个线程请求,只会有一个线程执行,其余全部堵塞,极大影响性能,不推荐使用。
4、双重校验锁-线程安全
public class Signleton {
private volatile static Signleton uniqueInstance;
public static Signleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Signleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Signleton();
}
}
}
return uniqueInstance;
}
}
uniqueInstance = new Signleton();
实际上是有三个步骤:
为 uniqueInstance 分配内存空间
初始化 uniqueInstance
将 uniqueInstance 指向分配的内存地址
在JVM优化中有一项指令重排,为了保证提高处理器效率,实际执行的顺序有可能与代码书写的顺序不同。在这三个步骤中,执行顺序有可能编程1->3->2,也就是说线程可能获得一个没有初始化的对象。
声明对象为volatile,该关键字防止指令重排序,保证了多线程下正常运行。
双重校验的意义在于,可能多个线程去尝试获取Singleton类锁,在获取到锁时,有可能另一个线程以及完成实例化,重新获取锁后重新校验,确保对象只实例一次。
5、枚举实现
public enum Signleton {
INSTANCE;
private Something something;
Signleton() {
something = new Something();
}
public Something getSomething() {
return something;
}
}
枚举类类似于静态内部类,提供了序列化机制,绝对防止多次实例化,及时面对复杂的序列化或者反射攻击。在《Effective Java》中提到单元素枚举类型已经成为实现Singleton的最佳方法。
网友评论