一、单列的6种写法归纳如下
1.1 饿汉式 (不支持延时加载)
-饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。
package com.mashibing.singleton.demo01;
/**
* 单例模式-饿汉式
* 在类加载期间初始化私有的静态实例,保证instance实例创建过程是线程安全的.
* 特点: 不支持延时加载,获取实例对象的速度比较快,但是如果对象比较大,而且一直没有使用就会造成内存的浪费.
**/
public class Singleton_01 {
//1. 私有构造方法
private Singleton_01(){
}
//2. 在本类中创建私有静态的全局对象
private static Singleton_01 instance = new Singleton_01();
//3. 提供一个全局访问点,供外部获取单例对象
public static Singleton_01 getInstance(){
return instance;
}
}
1.2 懒汉式(线程不安全) 支持延时加载
- 相对于饿汉式的优势是支持延迟加载。这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。
**
* 单例模式-懒汉式
* 特点: 支持延时加载,只有调用getInstance方法时,才会创建对象.
**/
public class Singleton_02 {
//1. 私有构造方法
private Singleton_02(){
}
//2. 在本类中创建私有静态的全局对象
private static Singleton_02 instance;
//3. 通过判断对象是否被初始化,来选择是否创建对象
public static Singleton_02 getInstance(){
if(instance == null){
instance = new Singleton_02();
}
return instance;
}
}
1.3 懒汉式(线程安全) 方法被同步锁锁住,降低了程序的效率,当多线程调用当实例的方法的时候就相当于串行
import com.mashibing.singleton.demo02.Singleton_02;
/**
* 单例模式-懒汉式(线程安全)
* 使用synchronized锁 锁住创建单例对象的方法,防止多个线程同时调用.
* 缺点: 因为对getInstance() 方法加了锁,导致这个函数的并发度很低.
**/
public class Singleton_03 {
//1. 私有构造方法
private Singleton_03(){
}
//2. 在本类中创建私有静态的全局对象
private static Singleton_03 instance;
//3. 通过添加synchronize,保证多线程模式下的单例对象的唯一性
public static synchronized Singleton_03 getInstance(){
if(instance == null){
instance = new Singleton_03();
}
return instance;
}
}
1.4 懒汉式(双重校验)
- 双重检测实现方式既支持延迟加载、又支持高并发的单例实现方式。只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中。所以,这种实现方式解决了懒汉式并发度低的问题。
注释:用 volatile 修饰1、保证变量的可见性,也就是被volatile修改的变量,在一个线程被修改的时候其他线程会立即得到修改和结果,2、volatile屏蔽指令重排序-volatile修饰的任何内存在他前面和后面都加了内存屏障
import com.mashibing.singleton.demo03.Singleton_03;
/**
* 单例模式-双重校验
**/
public class Singleton_04 {
//1. 私有构造方法
private Singleton_04(){
}
//2. 在本类中创建私有静态的全局对象
// 使用 volatile保证变量可见性,屏蔽指令重排序
private volatile static Singleton_04 instance;
//3. 获取单例对象的静态方法
public static Singleton_04 getInstance(){
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if(instance == null){ Double check Lock
synchronized (Singleton_04.class){
//第二次判断,抢到锁之后再次进行判断,判断是否为null
if(instance == null){
instance = new Singleton_04();
/**
* 上面的创建对象的代码,在JVM中被分为三步:
* 1.分配内存空间 new 半始化
* 2.初始化对象
* 3.将instance指向分配好的内存空间
Object o = new Object ()连问
1. 请解释一下对象的创建过程?(半初始化)
new 申请内存空间- 成员变量的默认值 m = 0;这个时候是半初始化状态
invokespecial 调用他的构造方法 变量的值变为8
astore t和m建立关联 将值赋给分配的内存
*/
}
}
}
return instance;
}
}
1.5 静态内部类创建单列 (特性 只有在去使用到的时候才去被创建或加载)推荐使用
- 利用 Java 的静态内部类来实现单例。这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单。(内部其实就是恶汉模式实现的)
package com.mashibing.singleton.demo05;
/**
* 单例模式-静态内部类(懒加载)
* 根据静态内部类的特性,同时解决了 延时加载 线程安全的问题,并且代码更加简洁
**/
public class Singleton_05 {
private Singleton_05(){
if(SingletonHandler.instance != null){
throw new RuntimeException("不允许非法访问");
}
}
//创建静态内部类
private static class SingletonHandler{
//在静态内部类中创建单例,在装载内部类的时候,才会创建单例对象
private static Singleton_05 instance = new Singleton_05();
}
public static Singleton_05 getInstance(){
return SingletonHandler.instance;
}
}
1.6 枚举单列 解决上面反射和序列化对单列的破坏
- 最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性(同时阻止了反射和序列化对单例的破坏)。
package com.mashibing.singleton.demo06;
/**
* 单例模式-枚举
* 阻止反射的破坏: 在反射方法中不允许使用反射创建枚举对象
* 阻止序列化的破坏: 在序列化的时候仅仅是将枚举对象的name属性输出到了结果中,反序列化的时候,就会通过
* Enum的 valueOf方法 来根据名字去查找对应枚举对象.
**/
public enum Singleton_06 {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Singleton_06 getInstance(){
return INSTANCE;
}
}
二、总结-反射和序列化对单列的破坏
反射对单列的破坏
-java中反射机制是指在运行状态中中对于任意一个类都能知道这个类所有属性和方法,对于任意一个对象都能调用他的任意属性和方法,这种动态获取和调用获取属性和方法的方式在java语言叫做反射
package com.mashibing.singleton.test02;
import com.mashibing.singleton.demo05.Singleton_05;
import com.mashibing.singleton.demo06.Singleton_06;
import java.lang.reflect.Constructor;
/**
* 反射对于单例的破坏
**/
public class Test_Reflect {
public static void main(String[] args) throws Exception {
Class<Singleton_06> clazz = Singleton_06.class;
Constructor<Singleton_06> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); //设置为true后,就可以对类中的私有成员进行操作
// Singleton_06 instance = constructor.newInstance();
Singleton_06 instance = constructor.newInstance(String.class, int.class);
System.out.println(instance);
}
}
运行结果 false
解决反射对单列的破环
public class Singleton_05 {
private Singleton_05(){
*****在这里添加判断解决反射对单列的破坏*****
if(SingletonHandler.instance != null){
throw new RuntimeException("不允许非法访问");
}
}
//创建静态内部类
private static class SingletonHandler{
//在静态内部类中创建单例,在装载内部类的时候,才会创建单例对象
private static Singleton_05 instance = new Singleton_05();
}
public static Singleton_05 getInstance(){
return SingletonHandler.instance;
}
}
2、序列化对单列的破坏
import com.mashibing.singleton.demo06.Singleton_06;
import org.junit.Test;
import java.io.*;
/**
* 序列化对单例的破坏
**/
public class Test_Serializable {
@Test
public void test() throws Exception{
//序列化对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile.obj"));
oos.writeObject(Singleton_06.getInstance());
//序列化对象输入流
File file = new File("tempFile.obj");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton_06 singleton06 = (Singleton_06) ois.readObject();
System.out.println(singleton06);
System.out.println(Singleton_06.getInstance());
System.out.println(Singleton_06.getInstance() == singleton06);//false
}
}
解决序列化对单列的破坏
/**
* 单例类实现序列化接口
*/
class Singleton implements Serializable {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
/**
* 只需要在单例类中定义readResolve方法,就可以解决序列化对于单例的破坏
* 程序会判断是否有readResolve方法,如果有就执行该方法,如果不存在---就会创建一个新的对象
*/
private Object readResolve(){
return singleton;
}
}
综上-解决反射和序列化对单列的破坏采用上面 -6、枚举单列写法