单例模式
单例模式在诸多设计模式中是属于很简单,也很好理解的设计模式了。
在应用程序中,某个类的实例对象只有一个,你没有办法去new一个新的对象,因为它的构造函数是私有的,只提供了一个对外开放获取实例对象的引用的方法(注意 :是实例对象的引用,并不是直接获取该实例对象本身,只是它的引用)
单例模式有很多实现方式,如:
- 懒汉模式(分为线程安全和线程不安全两种写法)
- 饿汉模式(线程安全)
- 静态内部类
- 枚举
- 双重校验锁
下面看具体实现
懒汉模式
线程不安全的写法,也是最简单的写法
public class Singleton{
private static Singleton singleton;
private Singleton(){}
public static Singleton getSingleton(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
以上就是一种懒汉式写法,但他是线程不安全的,我打个比方:
在多线程环境下,第一个线程调用了getSingleton()方法,该线程进入判断后,还没生成实例对象的时候,资源就被另一个线程占用了,而另一个线程同样也调用了这个方法,然后一路执行完毕,这个时候就有了一个实例对象了,接着资源重新分配到第一个线程,它继续执行之前未完成的步骤,又创建了一个实例对象,这个时候就有了2个实例对象了,所以它是线程不安全的
第二种写法
public class Singleton{
private static Singleton singleton;
private Singleton(){}
public static synchronized Singleton getSingleton(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
这种是线程安全的写法,在方法上加上了synchronized关键字修饰,把该方法变成了同步方法。
当一个线程正在执行这个方法,还没有执行完毕的时候,另外的线程是不能搞访问该方法的,所以一个线程调用该方法执行完毕后,生成一个实例对象,并返回它的引用,另一个线程再来调用这个方法的话,就进不了判断了,直接返回该实例对象的引用,所以他是线程安全的
饿汉模式
饿汉模式是线程安全的
pubilc class Singleton{
private static Singleton singleton = new Singleton();
private Singleton(){}
private Singleton getSingleton(){
return singleton
}
}
因为在创建对象时候用了static修饰了,所以在该类被加载的时候,就会直接创建该类的实例对象,后面获取实例引用的时候不会造成线程安全的问题。
静态内部类
静态内部类的实现方式,就是在该单例类中,再创建一个静态类,获取该单例类实例后,由内部类创建实例对象再返回出去,是线程安全的。
public class Singleton{
private static class SingletonHolder{
private Singleton SINGLETON = new Singleton();
}
private Singleton(){}
private Singelton getSingleton(){
return SingletonHolder.SINGLETON;
}
}
注意,静态内部类并不会随着外部类的加载而直接加载,而是在调用该静态方法的时候才会加载,这其实也是一种懒加载的实现。
枚举
public enum Singleton {
INSTANCE;
public static Singleton whateverMethod(){
return INSTANCE;
}
}
双重校验锁
顾名思义,双重校验,就是有2重判断,代码实现:
public class Singleton{
private volatile static Singleton singleton;
private Singleton(){}
public static Singleton getSingleton(){
//第一次校验
if(singleton == null){
synchronized(Singleton.class){
//第二次校验
if(Singleton==null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
该实现方法是线程安全的,它内部有两重实现第一层在刚进入方法的时候,会判断一次单例对象是否为空,如果不为空则直接返回该对象的引用,否则继续进入方法内部。第一次判断主要是为了效率,如果不为空就不进入,直接返回
可以看到进入后有一个同步代码块,而第二个判断在同步代码块,这样做是为了线程安全。打个比方:
当第一个线程调用了该方法后,先进入第一次校验,进入过后还没来得及执行同步代码块,资源就被第二个线程抢走了,所以这个时候还没有生成实例,这个时候,第二个线程也调用一次这个方法,进入第一重判断后继续执行,然后执行同步代码块,继续进入第二重判断,然后生成实例对象退出,这个时候第一个线程回过神来,继续执行同步代码块,然后第二重判断已经有了实例对象,退出!
如果没有第二重判断,那么第一个线程会再创建一次实例对象,所以这个时候就有2个实例对象,卒!
同时需要注意,在里面有一个关键字volatile,他是一个特征修饰符,作用是指令关键字,实现可见性和有序性。
101万+






