单例模式:保证一个类仅有一个实例,并提供一个访问他的全局点。
懒汉式,线程不安全
//懒汉式,线程不安全
public class Sington {
private Sington(){};//让外界不能通过new来创建实例
private static Sington instance;
public static Sington getInstance(){ //如果实例存在则直接返回,如果没有存在创建实例返回
if(instance==null){
instance=new Sington();
}
return instance;
}
public static void main(String[] args){
Sington s1=Sington.getInstance();
Sington s2=Sington.getInstance();
if(s1==s2){
System.out.println("相同实例");
}else {
System.out.println("不是相同实例");
}
}
}
上述例子如果实在并发情况下,就不能实现单例的效果。比如线程A在判断instance为空时,进入new操作,但是操作还没有完成,此时线程B刚刚好也运行到判断instance是否为null,那么就可能造成线程B也会就new一个实例。这样就违背了单例原本的含义。
第一步优化:为了解决多线程的同步问题我们可以使用synchronized关键字。
public static synchronized Sington getInstance(){ //如果实例存在则直接返回,如果没有存在创建实例返回
if(instance==null){
instance=new Sington();
}
return instance;
}
使用synchronized关键字可以保证单例,但是这时候就会有性能的问题,因为getInstance整个方法都是线程同步的,这样就限定了访问的速度。这时候我们进行第二次优化
双重检验索 线程安全
public class Singleton {
private static Singleton instance; //声明静态的单例对象的变量
private Singleton(){};//私有构造方法
public static Singleton getInstance(){//外部通过此方法可以获取对象
if(instance==null){
synchronized (Singleton.class){//保证了同一时间只能只能有一个对象访问此同步块
if(instance==null){
instance=new Singleton();
}
}
}
return instance;//返回创建好的对象
}
}
双重检验锁模式:是一种使用同步块加锁的方法。因为有两次检查,一次在同步块外,一次在同步块内。为什么同步块内还要检验一次呢?因为可能会有多个线程一起进入同步块外的if,如果在同步块内不进行就可能生成多个实例了。这段代码看起来没有问题,其实不然。主要在于instance=new Singleton(),这并非一个原子操作,实际上在
JVM中大概会做三件事。
1、给instance分配内存
2、调用Singleton的构造函数来初始化成员变量
3、将instance对象指向分配的内存空间。(执行完这步instance就是非null了)
但是JVM在即时编译器中存在指令重排序的优化。也就是说上面的第2、3步顺序是不能保证的,执行的顺序可能是1-2-3或者1-3-2。如果是后者,则在3执行完毕、2未执行之前,被线程二抢占了,这是instance已经是非null了(但是却没有初始化),所以线程二就会直接返回instance,然后使用,就会报错
解决方法:只需要将instance声明称volatile就可以了。使用原因:volatile是禁止指令重排序优化。也就是说在volatile变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会重排序到内存屏障之前
饿汉式 线程安全
public class Singleton{
//类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
这种方法非常简单,因为单例的实例对象被声明成了static和final变量,在第一次加载类到内存时就会进行初始化,所以创建实例本身是线程安全的。但是这个模式也有缺点,就是不管是否调用实例,都已经加载到了内存中。
静态内部类 线程安全
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种写法仍然使用JVM本身机制保证了线程安全的问题,由于SingletonHolder是私有的,除了本类中的getInstance方法没有办法访问他,所以他其实是懒汉式的。而在读取的实例的时候不会进行同步,又没有性能缺陷。也不依赖于jdk的版本,所以建议使用。