Java单例设计

需求一:

    保证类的实例在一个JVM内部只有一份(多线程共享)

解决方案

  1)确保类的外界最好不能直接构建对象
  2)将类的实例放在池中?(整数池,字符串池,...)

方案落地:

方案1:类加载时创建实例对象,应用场景:小对象(占用内存比较小)

--->饿汉式(线程安全,调用效率高,但是不能延时加载):

class Singleton01{
	//private byte[]array=new byte[1024*1024];
	//1.构建方法私有化
	private Singleton01() {}
	//2.类的内部构建对象
	private static  Singleton01 instance=new Singleton01();
	//3.对外提供对象访问
	public static Singleton01 getInstance() {
		return instance;
	}
	public static void show() {}
	public void display() {}
}

思考:假如show方法调用次数很多,而display方法最后调用或者调用很少,会导致一开始Singleton01对象创建,如果对象很大,会造成资源浪费.

方案2:类的实例何时需要何时创建,应用场景:大对象(占用内存比较多),稀少用;

--->懒汉式(线程安全,调用效率不高,但是能延时加载):

class Singleton02{
	//private byte[]array=new byte[1024*1024];
	//1.构建方法私有化
	private Singleton02() {}
	//2.类的内部构建对象
	private static  Singleton02 instance;
	//3.对外提供对象访问(会有阻塞)
	public synchronized static Singleton02 getInstance() {
		if(instance==null) {
			System.out.println("create()");
			instance=new Singleton02();
		}
		return instance;
	}
	public static void show() {}
	public void display() {}
}

思考:多线程情况下,需要加锁,如下代码如不加锁,对象会构建两次.加锁多线程会阻塞

	static void doMethod02() {
		Thread t1=new Thread() {
		   @Override
		   public void run() {
			  Singleton02.getInstance();
			  Singleton02.getInstance();
		   }	
		};
		Thread t2=new Thread() {
			@Override
			public void run() {
				Singleton02.getInstance();
				Singleton02.getInstance();
			}	
		};
		t1.start();
		t2.start();
	}

	public static void main(String[] args) {
		doMethod02();
	}

输出结果:

create()
create()

方案3:在方案2的基础上减少阻塞,双重验证减少大量阻塞

--->Double CheckLock实现单例:DCL也就是双重锁判断机制(由于JVM底层模型原因,偶尔会出问题,不建议使用):

volatile关键字的作用?
    1)保证多线程之间变量的可见性(一个线程修改了此变量的值,其它的立刻可见)

               --------参考文章:https://blog.csdn.net/qianzhitu/article/details/103052040

   2)禁止指令重排序(JVM内部对指令执行有优化)

class Singleton03{//应用场景:大对象(占用内存比较多),稀少用
	//private byte[]array=new byte[1024*1024];
	//1.构建方法私有化
	private Singleton03() {}
	//2.类的内部构建对象
	private static volatile Singleton03 instance;
	//3.对外提供对象访问(会有阻塞)
	//3.1多个线程并发访问此方法是否会有线程不会被阻塞?(有的)
	//3.2为什么synchronized内部还要有一次判断?(确保对象只创建1次)
	public static Singleton03 getInstance() {
		if(instance==null) {
			synchronized(Singleton03.class) {
				System.out.println("synchronized");
				if(instance==null) {
					//System.out.println("create()");
					instance=new Singleton03();
					//对象创建过程(开辟内存,初始化属性,调用构造方法,为instance赋值)
				}
			}
		}
		return instance;
	}
}

方案4:基于内部类实现对象的延迟加载在方案3的基础上继续减少阻塞,同时优化资源使用,应用场景:大对象(延迟加载),频繁用

--->静态内部类实现模式(线程安全,调用效率高,可以延时加载)

          注意:静态内部类中才允许写静态变量,非静态内部类,不允许写静态变量(语法如此)

class Singleton04{
	//private byte[] array=new byte[1024*1024];
	private Singleton04() {}
	//Singleton04加载时不会加载Inner类
	private static class Inner{
		private static final Singleton04 instance=new Singleton04();
	}
	//可以频繁访问(没有阻塞)
	public static Singleton04 getInstance() {
		//基于内部类实现对象的延迟加载
		return Inner.instance;
	}
	//public static void show() {}
	//public void display() {}
}

思考:当调用show方法时,不会导致array变量初始化,也不会导致Inner类加载;通过调用getInstance方法获取实例时,调用Inner.instance,会导致静态内部类Inner被加载,初始化intance,进而执行new Singleton04(),导致array被初始化,实现大对象的延迟加载.

方案5:基于枚举;应用场景:小对象,频繁用

--->枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用)

enum Singleton05{//Singleton05.class
	INSTANCE;//此对象可以延迟加载吗?不可以
	//private byte[] array=new byte[1024*1024];
	public static void show() {}
}

调用:

	static void doMethod05() {
		Singleton05.INSTANCE.show();
	}

思考:枚举对象类加载时构建对象,不能做延迟加载,如果是大对象array,在类加载时就占用资源,不合适;所以只适合小对象,频繁使用场景;

如何选用:

-单例对象 占用资源少,不需要延时加载,枚举 好于 饿汉

-单例对象 占用资源多,需要延时加载,静态内部类 好于 懒汉式

需求二:

        保证类的实例在一个线程内部只有一份(线程内部单例)

 要求:

        1)此类的实例每个线程只有一份(线程内部单例)
        2)在外界不允许构建此对象

方案落地:基于ThreadLocal实现

package com.java.design;

class Looper{//迭代器(任意的一个对象)
	private Looper() {
		System.out.println("Looper()");
	}
	private static ThreadLocal<Looper> td=new ThreadLocal<>() ;
	public static Looper getLooper() {
		//1.从当前线程获取looper对象
		Looper looper=td.get();//key是谁?
		//2.当前线程没有则创建looper并绑定到当前线程
		if(looper==null) {
			looper=new Looper();
			td.set(looper);//key是谁?ThreadLocal
		}
		//3.返回looper实例
		return looper;
	}
}
public class TestSingleton02 {
	public static void main(String[] args) {
		for(int i=0;i<3;i++) {
			new Thread() {
				@Override
				public void run() {
					Looper.getLooper();
					Looper.getLooper();
					Looper.getLooper();
				}
			}.start();
		}
	}
}

输出结果:

Looper()
Looper()
Looper()

  

猜你喜欢

转载自blog.csdn.net/qianzhitu/article/details/103047262
今日推荐