单例模式的写法
饿汉式是绝对线程安全的,但是会导致一个问题,不管用不用都会创建对象,多个无用的单例对象,造成不必要的内存浪费,拖慢系统的启动过程
// 饿汉式两种写法
class Hungry {
private static final Hungry instance = new Hungry();
private Hungry() {
}
}
class Hungry2 {
private static final Hungry2 instance;
static {
instance = new Hungry2();
}
private Hungry2() {
}
}
为了解决饿汉式内存浪费的问题,引入了懒汉模式,当需要使用的时候再初始化.
// 懒汉模式
class Lazy {
private static Lazy lazy=null;
private Lazy(){}
public static Lazy getInstance(){
if(lazy==null){
lazy=new Lazy();
}
return lazy;
}
}
但是上面的写法有线程安全的问题:在多线程的情况下,如果多个线程先后进入getInstance()
方法,都发现lazy==null,于是都去new对象了,就会导致两次对象不一致,虽然有时候可能是一样的
为了解决懒汉式的线程安全问题,可以给getInstance()
加synchronized关键字
/**
* 懒汉模式加synchronized关键字
*/
class Lazy {
private static Lazy lazy=null;
private Lazy(){}
public synchronized static Lazy getInstance(){
if(lazy==null){
lazy=new Lazy();
}
return lazy;
}
}
这时候,就算多线程,每次也只能有一个线程进入方法内部,其他线程只能在方法外等待(MONITOR),等待获得锁的线程执行完才能进入,因为之前的线程已经初始化好了,所以已经实现了线程安全
但是又引来一个新的问题,synchronized锁每一个时刻都只能有一个线程能访问getInstance()
,其他线程只能等待,大大降低了CPU的利用率
于是又有了新的改进
/**
* 懒汉模式加synchronized关键字
* 改进,让多个线程都能进入方法内部
*/
class Lazy {
private static Lazy lazy=null;
private Lazy(){}
public static Lazy getInstance(){
synchronized(Lazy.class){
if(lazy==null){
lazy=new Lazy();
}
}
return lazy;
}
}
但是这样还是和之前的一样,虽然多个线程能进来了,CPU稍微能提高了那么一丢丢,但是效果还是差不多
针对之前的问题,我们可以这样改进
/**
* 懒汉模式加
* 双重检查锁
*/
class Lazy {
private static Lazy lazy = null;
private Lazy() {
}
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
}
双重检查锁在lazynull的时候才进行锁住,但是这样还是有一个问题,如果两个线程都进入,都发现lazynull,一个线程锁住了,然后开始new Lazy(),看似没什么问题,但是在new Lazy()的过程中是会存在一点点问题:CPU指令重排
new一个对象,需要分配内存空间,引用指向内存空间,new 实例,很有可能引用非空,但是实例还没有new出来,这样会导致一个问题,当另一个线程进来,发现引用非空,直接使用了,导致空指针异常
所以还需要对可见性进行控制
/**
* 懒汉模式加
* 双重检查锁
*/
class Lazy {
private static volatile Lazy lazy = null;
private Lazy() {
}
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
}
volatile关键字保证了线程间共享变量的可见性,至此,懒汉模式终于成功的实现了线程安全单例.但是貌似不够优雅啊
/**
* 静态内部类实现单例,绝对的线程安全
* 只有外部类被使用,静态内部类才会被加载,也是懒加载
*/
class InnerClassSinglton {
private InnerClassSingleton(){
}
private static class InnerClass{
private static final InnerClassSinglton singlton=new InnerClassSinglton();
}
public InnerClassSinglton getInstance(){
return InnerClass.singlton;
}
}
貌似单例已经完全ok了,但是还是有一个风险,反射破坏
public class TestSingleton {
public static void main(String[] args) throws Exception {
Class<InnerClassSingleton> singletonClass = InnerClassSingleton.class;
Constructor constructor = singletonClass.getDeclaredConstructor();
//暴力突突突破private
constructor.setAccessible(true);
Object instance1 = constructor.newInstance();
Object instance2 = constructor.newInstance();
Object instance3 = constructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance3);
//....直接能new出多个实例,这怎么行
}
}
反射直接搞出多个实例了,所以需要控制一下
//抛异常,禁用反射调用
class InnerClassSingleton {
private static class InnerClass{
private static final InnerClassSingleton singleton =new InnerClassSingleton();
}
public InnerClassSingleton getInstance(){
return InnerClass.singleton;
}
private InnerClassSingleton(){
if(InnerClass.singleton!=null){
throw new IllegalStateException("不允许非法获取实例");
}
}
}
这样,你再反射直接回抛异常,真正实现了单例
但是发现每个单例类都这么搞,不得累死,所以可以使用最优雅的方式来创建单例,enum
枚举
/**
* 枚举创建单例
*/
enum Singleton {
INSTANCE;
private Object data = new Object();
public Object getData() {
return data;
}
}
先看下这个类到底有哪些构造方法
public class SingletonTest {
public static void main(String[] args) throws Exception {
Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(Arrays.toString(constructor.getParameterTypes()));
}
}
}
可以发现有一个构造方法,参数类型为class java.lang.String, int
public class SingletonTest {
public static void main(String[] args) throws Exception {
/* Constructor>[] constructors = Singleton.class.getDeclaredConstructors();
for (Constructor> constructor : constructors) {
System.out.println(Arrays.toString(constructor.getParameterTypes()));
}*/
Constructor<? extends Singleton> constructor = Singleton.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Singleton singleton = constructor.newInstance("1",5);
System.out.println(singleton);
}
}
尝试暴力获取,发现直接报错了
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
我们可以看下Construct类的newInstance
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
发现jdk已经帮我们判断了,如果发现时枚举类型,直接报错,和我们之前手动抛异常一样,这样你想反射也反射不了,保证了单例,而且也不用自己写判断逻辑,是推荐实现单例的终极方式
测试一下
public class SingletonTest {
public static void main(String[] args) throws Exception {
Object data1 = INSTANCE.getData();
Object data2 = INSTANCE.getData();
System.out.println(data1);
System.out.println(data2);
System.out.println(data1 == data2);
}
}
每次获取到都是一样的,说明INSTANCE是一个单例对象,而且代码很简洁,根本不需要自己去控制线程安全不安全,也不用处理异常,jdk都帮你考虑好了,十分推荐这种单例模式