文章目录
1、单例介绍
1.1、单例模式使用场景
单例模式(Singleton Pattern )是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛。J2EE标准中的ServletContext
,ServletContextConfig
等、 Spring 框架应用中ApplicationContext
、数据库的连接池
等也都是单例形式。
1.2、单例的实现思路
- 静态化实例对象。
- 私有化构造方法,禁止通过构造方法创建实例。
- 提供一个公共的静态方法,用来返回唯一实例。
2、饿汉式单例
2.1、特点
饿汉式单例在类加载的时候就初始化创建对象。线程安全,但是占用、浪费内存。
2.2、标准饿汉式单例代码
public class HungrySingleton {
// 类初始化的时候就创建对象
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
2.3、静态代码块饿汉式单例
这种方式和直接使用静态属性的方式差异不大。可以说没有什么差异。只是利用静态代码块初始化对象。
public class HungryStaticSingleton {
//先静态后动态
//先上,后下
//先属性后方法
private static final HungryStaticSingleton hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
}
3、懒汉式单例
3.1、特点
在单例对象被调用的时候才初始化,不浪费内存,但是存在线程安全的问题。
3.2、标准懒汉式
通过加synchronized
d 的方式解决线程安全问题,降低了效率。
public class LazySimpleSingletion {
private static LazySimpleSingletion instance;
private LazySimpleSingletion(){}
public synchronized static LazySimpleSingletion getInstance(){
if(instance == null){
instance = new LazySimpleSingletion();
}
return instance;
}
}
3.3、双重检查方式(double check)
在LazyDoubleCheckSingleton
还没有创建的时候,如果存在多条线程调用getInstance()
方法,就会出现并发的问题。所以这里两条线程都会走到synchronized
同步代码块里面,这样再来一次判断,保证了LazyDoubleCheckSingleton
只会被创建一次。
当LazyDoubleCheckSingleton
创建以后,这时候不管有多少线程来访问getInstance()
方法,通过第一次判断就获得LazyDoubleCheckSingleton
对象,就不需要再获取锁,所以对比标准模式,提高了执行效率。
public class LazyDoubleCheckSingleton {
// volatile 防止指令重排序的问题
private volatile static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
//检查是否要阻塞
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
//检查是否要重新创建实例
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
}
如果不明白为什么要加volatile
关键字,戳这里
4、最牛单例
这个单例是基于懒汉式模式,通过静态内部类的特性(不会自动初始化。只有调用静态内部类的方法,静态域,或者构造方法的时候才会加载静态内部类)解决线程安全和内存浪费的问题。java类加载顺序
public class LazyStaticInnerClassSingleton {
// 构造方法私有,只能通过统一入口获取对象
private LazyStaticInnerClassSingleton(){
// 这里防止通过反射强制访问创建对象
if(LazyHolder.INSTANCE != null){
throw new RuntimeException("不允许非法访问");
}
}
// 提供唯一的访问入口,static修饰不会被重写
private static LazyStaticInnerClassSingleton getInstance(){
return LazyHolder.INSTANCE;
}
// 通过静态内部类的方式,实现再对象被调用到的时候才创建
private static class LazyHolder{
private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
}
}
5、破坏单例的方法
5.1、反射破坏单例
通过反射的方式,强制访问构造方法,创建对象。
public static void main(String[] args) {
try {
Class<?> clazz = LazyStaticInnerClassSingleton.class;
// 通过反射获取私有构造函数
Constructor c = clazz.getDeclaredConstructor(null);
// 强制访问
c.setAccessible(true);
// 暴力初始化
Object instance1 = c.newInstance();
// 再次暴力初始化
Object instance2 = c.newInstance();
System.out.println(instance1 == instance2); // false
}catch (Exception e){
e.printStackTrace();
}
}
反射破坏单例的方式,可以通过最牛单例中的如下代码解决
// 构造方法私有,只能通过统一入口获取对象
private LazyStaticInnerClassSingleton(){
// 这里防止通过反射强制访问创建对象
if(LazyHolder.INSTANCE != null){
throw new RuntimeException("不允许非法访问");
}
}
5.2、序列化破坏单例
例如,可以通过如下代码来破坏单例
// 单例类
public class SerializableSingleton implements Serializable {
public final static SerializableSingleton INSTANCE = new SerializableSingleton();
private SerializableSingleton(){}
public static SerializableSingleton getInstance(){
return INSTANCE;
}
}
// 测试类
public class SerializableSingletonTest {
public static void main(String[] args) {
SerializableSingleton s1 = null;
SerializableSingleton s2 = SerializableSingleton.getInstance();
FileOutputStream fos = null;
try {
// 序列化
fos = new FileOutputStream("SerializableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SerializableSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
/**
* 输出结果
* com.gupaoedu.vip.pattern.singleton.seriable.SeriableSingleton@5fe5c6f
* com.gupaoedu.vip.pattern.singleton.seriable.SeriableSingleton@5fe5c6f
* false
*/
} catch (Exception e) {
e.printStackTrace();
}
}
}
预防方法,可以通过添加readResolve()
方法即可,如下代码测试
// 单例类
public class SerializableSingleton implements Serializable {
public final static SerializableSingleton INSTANCE = new SerializableSingleton();
private SerializableSingleton(){}
public static SerializableSingleton getInstance(){
return INSTANCE;
}
// 添加此方法后,反序列化后的对象和序列化之前的对象是一个对象
private Object readResolve(){ return INSTANCE;}
}
// 测试类
public class SerializableSingletonTest {
public static void main(String[] args) {
SerializableSingleton s1 = null;
SerializableSingleton s2 = SerializableSingleton.getInstance();
FileOutputStream fos = null;
try {
// 序列化
fos = new FileOutputStream("SerializableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SerializableSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
/**
* 输出结果
* com.gupaoedu.vip.pattern.singleton.seriable.SeriableSingleton@5fe5c6f
* com.gupaoedu.vip.pattern.singleton.seriable.SeriableSingleton@5fe5c6f
* true
*/
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里通过添加readResolve()
解决了单例的问题,但是单例还是实例化了两次,只是第二次返回的是第一次的地址。
6、注册式单例
注册式单例又称登记式单例,是把每个实例对象登记到某个地方,通过唯一标识获取实例。注册式单例模式有两种,一种枚举单例模式,另一种容器式单例模式。
6.1、枚举式单例模式
枚举单例的代码示例
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){return INSTANCE;}
}
我个人对枚举单例的使用理解不太透彻,如下单例改造成枚举单例的案例仅供参考:
// 正常单例
public class PoolWorker {
private final static Logger log = LoggerFactory.getLogger(PoolWorker.class);
// 构造函数私有
private PoolWorker() {
if(InitPoolWorker.poolWorkers != null){
throw new RuntimeException("PoolWorker has been created!");
}
}
// 唯一访问入口
public static ExecutorService getPoolWorkers() {
return InitPoolWorker.poolWorkers;
}
// 通过静态内部类的方式初始化,被调用的时候才初始化
private static class InitPoolWorker{
private static ExecutorService poolWorkers = new ThreadPoolExecutor(
10,
20,
1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(5),
(Runnable r) -> {
log.info("线程{}创建",r.hashCode());
Thread th = new Thread(r,"threadPool"+r.hashCode());
return th;
},
new ThreadPoolExecutor.CallerRunsPolicy());
}
}
// 改造后枚举单例
public enum PoolWorker2 {
INSTANCE;
private final static Logger log = LoggerFactory.getLogger(PoolWorker.class);
// 唯一访问入口
public static ExecutorService getPoolWorkers() {
return InitPoolWorker.poolWorkers;
}
// 通过静态内部类的方式初始化,被调用的时候才初始化
private static class InitPoolWorker{
private static ExecutorService poolWorkers = new ThreadPoolExecutor(
10,
20,
1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(5),
(Runnable r) -> {
log.info("线程{}创建",r.hashCode());
Thread th = new Thread(r,"threadPool"+r.hashCode());
return th;
},
new ThreadPoolExecutor.CallerRunsPolicy());
}
}
在我看来,单例枚举的使用,仅仅是通过枚举的特性,解决正常单例会出现的反射和序列化破坏单例的问题。但是,在使用的时候还是通过通过静态内部类单例或者double check 的方式来实现。
6.2、容器式单例模式
容器式单例模式,适用于需要创建大量单例对象的场景,便于管理。在spring的IOC中也使用到。
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
public static synchronized 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);
}
}
}