Java单例模式--9种写法

概念:什么是单例模式?

答:单例模式(Singleton Pattern):是指确保一个类在任何情况都绝对只有一个实例,并且提供一个全局访问点。在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例。

单例模式的特点:

  • 单例类只有一个实例对象;
  • 该单例对象必须由单例类自行创建;
  • 单例类对外提供一个访问该单例的全局访问点;

单例模式的写法:

  • 1 饿汉-Hungry(可用)
  • 2 懒汉-Lazy(不推荐)
  • 3 线程安全-synchronized(可用)
  • 4 双重检测-DoubleCheckLock(推荐)
  • 5 内部类-InnerClass(推荐)
  • 6 序列化-Serializable(可用)
  • 7 容器单例-IOC(可用)
  • 8 枚举-Enum(推荐)
  • 9 ThreadLocal

逐一分析讲解单例模式的各种写法:

一,单例模式之饿汉-Hungry(可用)

饿汉单例模式在类加载的时候,就初始化,并且创建对象
优点:

  • 1.没有加任何锁,执行效率比较高
  • 2.用户体验上来说,比懒汉式更好
  • 3.线程绝对安全,线程还没有访问的时候,就已经初始化了,不存在访问线程安全问题

缺点:

  • 1.类加载的时候就初始化,不管你用不用,都占用内存资源

代码一:直接new

public class HungrySingleton {
 
    private static final HungrySingleton instance = new HungrySingleton();
    //私有化构造函数
    private HungrySingleton(){}
 
    public static HungrySingleton getInstance(){
        return instance;
    }
}

代码二:在静态代码块中进行初始化(静态代码块也是在类加载的时候执行):

public class HungryStaticSingleton {
 
    //私有化构造函数
    private HungryStaticSingleton() {}
 
    private static final HungryStaticSingleton instance;
    //静态代码块
    static {
        instance = new HungryStaticSingleton();
    }
 
    public HungryStaticSingleton getInstance() {
        return instance;
    }
}

二,单例模式之懒汉-Lazy(不推荐)

有线程调用getInstance方法的时候,开始进行初始化
优点:
1.开始不占用内存,只有调用了才初始化实例
缺点:
1.并发的时候,可能会出现多个实例。线程不安全,因为并发的时候,会出现创建多个实例的情况

代码:

public class LazySimpleSingleton {
 
    private static LazySimpleSingleton instance = null;
    //私有化构造函数
    private LazySimpleSingleton() {}
 
    public static LazySimpleSingleton getInstance() {
        if (instance == null) {
            instance = new LazySimpleSingleton();
        }
        return instance;
    }
}

代码:多线程调用

// 用两个线程来模拟并发
public class ExecutorThread implements Runnable {
    @Override
    public void run() {
        //获取实例
        LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
        //打印当前线程名和实例名
        System.out.println(Thread.currentThread().getName()+":"+instance);
    }
}
// 创建Test类,创建两个线程并启动
public class LazySimpleSingletonTest {
 
    public static void main(String[] args) {
 
        new Thread(new ExecutorThread()).start();
        new Thread(new ExecutorThread()).start();
       
    }
}

测试结果:两个线程输出的不是同一个对象。

三、单例模式之线程安全-synchronized(可用)

有线程调用getInstance方法的时候,开始进行初始化
优点:
1.开始不占用内存,只有调用了才初始化
2.线程安全,因为getInstance加上了锁,假如并发访问的话,拿到锁的线程,才能进入方法进行初始化,其他线程排队等待锁
缺点:
1.性能比较低,并发的时候,会出现其他线程等待的情况
2.每一次并发的时候,都会有锁的竞争情况

代码:

public class LazyThreadSingleton {
 
    private static LazyThreadSingleton lazySingleTon = null;
    //私有化构造函数
    private LazyThreadSingleton() {}
 
    public static synchronized LazyThreadSingleton getInstance() {
        if (lazySingleTon == null) {
            lazySingleTon = new LazyThreadSingleton();
        }
        return lazySingleTon;
    }
}

四、单例模式之DCL双重检测-DoubleCheckLock(推荐)

有线程调用getInstance方法的时候,开始进行初始化
优点:
1.开始不占用内存,只有调用了getInstance才初始化
2.第一次并发访问存在锁的竞争情况,后面任何一个一次并发都不会存在锁的竞争

缺点:
1.第一次并发的时候,有锁的竞争情况,存在线程等待
2.代码看起来不够优雅

代码:

public class LazyDoubleCheckSingleton {
    //私有化构造函数
    private LazyDoubleCheckSingleton() {}
 
    // 加上 volatile 关键字,解决 instance = new LazyDoubleCheckSingleton() 处的指令重排序问题
    private volatile static LazyDoubleCheckSingleton instance = null;
 
    public static LazyDoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

Java中new一个对象,在底层实现是多条语句,重点是 new(申请内存)、invokespecial(初始化)、astore(建立连接),如果 后两者发生指令重排序,此时对象已不为空,且此时其他线程来判断对象,此时得到的对象是一个半初始化的非空对象。

五、单例模式之内部类-InnerClass(推荐)

JVM加载LazyInnerClassSingleton的时候,不会去加载内部类LazyHolder,只有调用getInstance方法的时候,JVM才会去加载LazyHolder,巧妙的避开了线程安全的问题
优点:
1.开始不会占用内存
2.兼顾了synchronized的性能问题
3.利用Java本身的语法特点,巧妙的避免了线程安全的问题

缺点:写法不够优雅

代码:

public class LazyInnerClassSingleton {
    //私有化构造函数
    private LazyInnerClassSingleton() {
        // 避免反射破坏单例
        if(LazyHolder.INSTANCE != null){
            throw new RuntimeException("不允许非法访问");
        }
    }
 
    public static LazyInnerClassSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }
 
    //内部类
    private static class LazyHolder {
        private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
    }
}

六、单例模式之序列化-Serializable(可用)

两个要求:1.实现Seriablizable,2.增加readResolve方法

序列化:将对象或数据结构转为字节序列的过程。例如:网络IO、磁盘、内存中状态给永久保存下来;
反序列化:将序列化后生成的字节序列转为对象或数据结构的过程。

代码:

//实现Serializable
public class SeriableSingleton implements Serializable {
    //私有化构造函数
    private SeriableSingleton(){}
 
    private static final SeriableSingleton instance = new SeriableSingleton();
 
    public static SeriableSingleton getInstance(){
        return instance;
    }
    //一定要加上这个方法,否则序列化会破坏单例
    public Object readResolve(){
        return instance;
    }
}

对象进行序列化和反序列化的Test:

public class SeriableSingletonTest {
 
    public static void main(String[] args) {
        SeriableSingleton s1 = SeriableSingleton.getInstance();
 
        try {
            //序列化
            FileOutputStream fos = new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s1);
            oos.flush();
            oos.close();
            //反序列化
            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            SeriableSingleton s2 = (SeriableSingleton) ois.readObject();
            ois.close();
 
            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1==s2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试结果:  System.out.println(s1==s2);   结果为true

七、单例模式之容器单例-IOC(可用)

容器单例适用于创建非常多的实例,便于管理,就像Spring的IoC容器一样。

public class ContainerSingleton {

    private ContainerSingleton(){}

    private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();

    public static Object getInstance(String className){
        Object instance = null;
        if(!ioc.containsKey(className)){
            try {
                instance = Class.forName(className).newInstance();
                ioc.put(className, instance);
            }catch (Exception e){
                e.printStackTrace();
            }
            return instance;
        }else{
            return ioc.get(className);
        }
    }

}

八、单例模式之枚举-Enum(推荐)

优点:可以防止反射破坏单例

public enum EnumSingleton {
    INSTANCE;

    EnumSingleton(){
        System.out.println("新建对象");
    }

    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumSingleton getInstance(){return INSTANCE;}
}

测试代码:

public class EnumSingletonTest {
    public static void main(String[] args) {
        EnumSingleton instance1 = EnumSingleton.getInstance();
        EnumSingleton instance2 = EnumSingleton.getInstance();
        System.out.println(instance1==instance2);
    }
}

测试结果:true

代码:反射破坏单例测试

public class EnumSingletonTest {
    public static void main(String[] args) {
        EnumSingleton instance = EnumSingleton.getInstance();
        instance.setData(new Object());

        try {
            Class clazz = EnumSingleton.class;
            Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
            c.setAccessible(true);
            System.out.println("c="+c);
            Object o = c.newInstance(); // 报错java.lang.IllegalArgumentException: Cannot reflectively create enum objects
            System.out.println("o="+o);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

九,单例模式之ThreadLocal

ThreadLocal可以保证同一个线程中保留一份实例,对不同的线程,都有这个实例的一个副本,这样保证线程安全。

在不考虑反射攻击、序列化与反序列化破坏的情况下,ThreadLocal的单例模式的实现也有可取之处:

代码:

public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =
            new ThreadLocal<ThreadLocalSingleton>() {
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };

    private ThreadLocalSingleton() {}

    public static ThreadLocalSingleton getInstance() {
        return threadLocaLInstance.get();
    }
}

测试类:创建线程 ExectorThread

public class ExectorThread implements Runnable{

    public void run() {
//        LazySimpleSingletion instance = LazySimpleSingletion.getInstance();
//        LazyDoubleCheckSingleton instance = LazyDoubleCheckSingleton.getInstance();
        ThreadLocalSingleton instance = ThreadLocalSingleton.getInstance();
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(Thread.currentThread().getName() + ":" + instance);
    }
}

测试类Test:

public class ThreadLocalSingletonTest {

    public static void main(String[] args) {
        System.out.println(ThreadLocalSingleton.getInstance()); // ThreadLocalSingleton@4554617c
        System.out.println(ThreadLocalSingleton.getInstance()); // ThreadLocalSingleton@4554617c
        System.out.println(ThreadLocalSingleton.getInstance()); // ThreadLocalSingleton@4554617c

        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());
        t1.start();
        t2.start();
        System.out.println("------------------------------End");
    }
}

测试结果:从输出结果可以看到,同一个线程main中的对象是同一个,不同线程t1与t2中的对象不同。保证了一个线程中一个单例副本。

 

关于破坏单例的两种方式:

1,序列化:

2,反射:

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值