需求一:
保证类的实例在一个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()