Java创建线程安全的单例
单例的使用场景
JVM中仅需要一个实例,因此能节省内存,加快访问速度,比如数据库连接池,计数器等。Spring 中的Bean,默认也是单例的,共享资源的访问,比如日志文件,系统配置单例的实现
要实现一个单例,首先要把构造方法设置成私有的,并且要提供一个返回实例的方法
1、实现一个单例,单线程是安全的,多线程中不安全,代码如下:
单线程的单例
如果多线程下,如果第一个线程执行检查instance是否为null,如果为null,执行创建一个Singleton1的实例,在实例未被初始化完毕的时候,第二个线程检查instance是否为null,这时候是null,也会创建一个实例。这时候就会有两个实例。
2、多线程下的安全单例,第一种方式就是方法加锁来实现,比如添加关键字synchronized。
方法加锁来实现线程安全的单例
添加了一个synchronized关键字,实现了多线程下的安全单例,每次获取会锁住Class对象,导致性能不高。
3、多线程下的安全单例,第二种方式就是双重检查,下面的实现方式有问题吗?
实现双重检查
不在方法上添加synchronized,在instance为null的时候才会添加锁,这样效率提高了不少,但是在多线程下面可能会出现问题。原因是创建实例的时候,有几个步骤(分配空间,初始化,把instance执行分配的地址),这几个步骤会被处理器重排序,导致在没有完全初始化的情况下其他线程获取了实例,可能会导致莫名其妙的问题。
4、第三种方式实现多线程下的安全单例,在上个例子中,把instance设置为volatile,这个关键字能保可见性,其实是阻止了重排序(分配空间,初始化,把instance执行分配的地址)。
第三种方式实现多线程下的安全单例
5、第四种方式实现多线程下的安全单例,使用饿汉式,缺点就是不能延迟加载。
饿汉式多线程下的安全单例
6、第五种方式实现多线程下的安全单例,基于类的初始化,在类初始化的时候,JVM会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。
v