单例模式
基本概念
所谓的单例模式指的是,采取一定的方法,使得整个系统中一个类只有一个实例对象,该类只提供一个取得该实例的方法(静态方法)
单例模式适用场景
需要频繁进行创建和销毁的对象。创建对象时耗时过多浪费时间和资源。
实现方式
单例模式一共有七种:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 饿汉式(枚举)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块,双重判断)
- 懒汉式(静态内部类)
代码实现
1 .饿汉式(静态常量)
优点:类装载时就完成了实例化,不存在同步问题。
缺点:类装载时就完成了实例化,不使用的话就造成了内存的浪费
class singleTon{
private singleTon(){}
private final static singleTon instance=new singleTon();
public static singleTon getInstance(){
return instance;
}
}
- 饿汉式(静态代码块)
和静态常量基本相同,同样的缺点和优点。
class singleTon{
private singleTon(){}
private static singleTon instance;
static {
instance=new singleTon();
}
public static singleTon getInstance(){
return instance;
}
}
- 饿汉式(枚举)
优点:避免反序列化重新创建对象,实现了同步,java作者推荐使用的方式。
缺点:不能延时加载
enum single{
INSTANCE;
}
- 懒汉式(线程不安全)
优点:起到了懒加载的作用
缺点:线程不安全,实际开发中不用
class singleTon{
private singleTon(){}
private static singleTon instance;
public static singleTon getInstance(){
if (instance==null){
instance=new singleTon();
}
return instance;
}
public void test(){
System.out.println("创建");
}
}
- 懒汉式(线程安全,同步方法)
优点:起到了懒加载和创建时线程不同步的问题
缺点:效率低下,同一时间只能有一个线程得到对象,创建后各个线程应该可以随意得到对象
class singleTon{
private singleTon(){}
private static singleTon instance;
public synchronized static singleTon getInstance(){
if (instance==null){
instance=new singleTon();
}
return instance;
}
public void test(){
System.out.println("创建");
}
}
- 懒汉式(线程安全,同步代码块,双重判断)
volatile关键字保证可见性,防止指令重排
指令重排:分配内存,创建对象,引用指向对象顺序可能会乱,比如先引用指向对象然后再创建完成对象
class single{
private singleTon(){}
private static volatile singleTon instance;
public static singleTon getInstance(){
if (instance==null){
synchronized(singleTon.class){
if (instance==null){
instance=new singleTon();
}
}
}
return instance;
}
}
- 懒汉式(静态内部类)
优点:因为外部类加载时内部类不会一起加载,所以起到了懒加载的作用。使用inner.instance时内部类才会装载
class single{
private single(){}
private static class inner{
private static final single instance=new single();
}
public static single getInstance(){
return inner.instance;
}
}
问题
恶汉式、懒汉式的方式还不能防止反射来实现多个实例,通过反射的方式,设置ACcessible.setAccessible方法可以调用私有的构造器,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
private static int i = 1;
private Singleton() {
if(i==1){
i++;
}else{
throw new RuntimeException("只能调用一次构造函数");
}
}
这样还不能保证单例,当序列化后,反序列化是还可以创建一个新的实例,在单例类中添加readResolve()方法进行防止。
private Object readResolve(){
return instance;
}
JAVA中的使用
系统类Runtime用到了单例模式