目录
一、概念
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例设计模式分类两种:
- 饿汉式:类加载就会导致该单实例对象被创建
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
二、饿汉式
类加载就会导致该单实例对象被创建
1.静态变量
instance
对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
public class Single {
//私有构造方法,不予外部人员使用
private Single(){
}
//创建实例
private static Single instance=new Single();
//返回实例
public static Single getInstance(){
return instance;
}
}
2.静态代码块方式
在成员位置声明静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。同上一方法,当然该方式也存在内存浪费问题
public class Single {
//私有构造方法,不予外部人员使用
private Single(){}
//声明对象名
private static Single instance;
//在静态代码块中分配内存和指向对象
static {
instance=new Single();
}
//返回实例
public static Single getInstance(){
return instance;
}
}
三、懒汉式
类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
方式一(线程不安全)
public class Single {
//私有构造方法,不予外部人员使用
private Single(){}
//创建实例对象
private static Single instance=null;
//返回实例
public static Single getInstance(){
if(instance==null){
return new Single();
}
return instance;
}
}
线程不安全:当线程1在
instance==null
判断进入后,线程2执行instance ==null
判断也进入到下一步就会导致创建多个对象
方式二(线程安全)
在上一方式的基础上给
getInstance()
加锁,但是这样会严重影响性能
public class Single {
//私有构造方法,不予外部人员使用
private Single(){}
//创建实例对象
private static Single instance=null;
//返回实例
public static synchronized Single getInstance(){
if(instance==null){
return new Single();
}
return instance;
}
}
方式三(双重检查锁)
版本1
public class Single {
//私有构造方法,不予外部人员使用
private Single(){}
//声明静态变量
private static Single instance=null;
//返回实例
public static Single getInstance(){
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if (instance==null){
synchronized(Single.class){
//抢到锁之后再次判断是否为null
if (instance==null){
return instance=new Single();
}
}
}
return instance;
}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile
关键字,volatile
关键字可以保证可见性
和有序性
。
instance=new Single(); //不是一个原子性操作
创建对象的三个步骤
正常走
123
指令重排可能执行过程为132
这个时候线程1走完13时,线程2进行第一层if (instance==null)
判断,此时是可以通过了,instance已经指向空间了,直接返回return instance
,但是此时instance并未初始化,还是一个空指针
版本2
volatile 禁止指令重排
public class Single {
//私有构造方法,不予外部人员使用
private Single(){}
//声明静态变量
private static volatile Single instance=null;
//返回实例
public static Single getInstance(){
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if (instance==null){
synchronized(Single.class){
//抢到锁之后再次判断是否为null
if (instance==null){
return instance=new Single();
}
}
}
return instance;
}
}
方式四(静态内部类方式)
静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被
static
修饰,保证只被实例化一次,并且严格保证实例化顺序。
public class Single {
//私有构造方法,不予外部人员使用
private Single(){}
//创建实例
private static class SingleInstance{
private static final Single Instance=new Single();
}
//返回实例
public static Single getInstance(){
return SingleInstance.Instance;
}
}
第一次加载Single类时不会去初始化Instance,只有第一次调用getInstance,虚拟机加载并初始化Instance,这样不仅能确保线程安全,也能保证 Single 类的唯一性。
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
四、枚举方式(饿汉式方式)
自带单例,反射也不能破坏单例
public enum Single {
INSTANCE;
}
五、存在的问题和解决方式
序列化和反射可以破坏单例
1.序列化方式
//可序列化
public class Single implements Serializable {
//私有构造方法,不予外部人员使用
private Single(){}
//创建实例
private static class SingleInstance{
private static final Single Instance=new Single();
}
//返回实例
public static Single getInstance(){
return SingleInstance.Instance;
}
}
序列化破坏单列模式
public class test {
public static void main(String[] args) throws Exception {
//将Single的实例以流的形式写进test.txt文本中
writeObjectToFile();
//读取test.txt文本字节流并序列化为Single对象
Single single1 = readObjectFromFile();
Single single2 = readObjectFromFile();
System.out.println(single1==single2);//false 可见生成两个不同的对象
}
//将Single的实例以流的形式写进文本中
public static void writeObjectToFile() throws Exception {
Single single=Single.getInstance();
//创建对象输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:\\IDEA\\Design\\Single\\src\\demo5\\test.txt"));
//将single写进test.txt中
objectOutputStream.writeObject(single);
}
//读取文本中的字节流序列化成对象
private static Single readObjectFromFile() throws Exception {
//创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\IDEA\\Design\\Single\\src\\demo5\\test.txt"));
//第一个读取字节流并序列化为Single对象
Single instance = (Single) ois.readObject();
return instance;
}
}
解决方案
在Singleton类中添加
readResolve()
方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
public class Single implements Serializable {
//私有构造方法,不予外部人员使用
private Single(){}
//创建实例
private static class SingleInstance{
private static final Single Instance=new Single();
}
//返回实例
public static Single getInstance(){
return SingleInstance.Instance;
}
//使用序列化的方式创建对象会调用该方法,限制为单例
private Object readResolve() {
return SingleInstance.Instance;
}
}
2.反射方式
public class Single {
//私有构造方法,不予外部人员使用
private Single(){}
//声明静态变量
private static volatile Single instance=null;
//返回实例
public static Single getInstance(){
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if (instance==null){
synchronized(Single.class){
//抢到锁之后再次判断是否为null
if (instance==null){
return instance=new Single();
}
}
}
return instance;
}
}
反射破坏单例
public class test {
public static void main(String[] args) throws Exception {
//获取Singleton类的字节码对象
Class c = Single.class;
//获取Single类的私有无参构造方法对象
Constructor constructor = c.getDeclaredConstructor();
//取消访问检查
constructor.setAccessible(true);
Single s1 = (Single) constructor.newInstance();
Single s2 = (Single) constructor.newInstance();
//判断通过反射创建的两个对象是否是同一个对象
System.out.println(s1 == s2);
}
}
解决方案
- 版本1
public class Single {
//私有构造方法,不予外部人员使用
private Single(){
//反射调用时报错,禁止反射调用
synchronized(Single.class){
if(instance != null) {
throw new RuntimeException();
}
}
}
//声明静态变量
private static volatile Single instance=null;
//返回实例
public static Single getInstance(){
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if (instance==null){
synchronized(Single.class){
//抢到锁之后再次判断是否为null
if (instance==null){
return instance=new Single();
}
}
}
return instance;
}
}
上面的还是可以被破坏
构造方法中使用
instance != null
进行判断,一开都不使用getInstance
方法,都是用反射获取对象就破坏了
- 版本2
限制反射只能获取一次对象
public class Single {
//私有构造方法,不予外部人员使用
private static boolean flag=false;
private Single(){
synchronized (Single.class){
if(flag===false){
flag=true;
}{
throw new RuntimeException("不要试图反射破坏单例");
}
}
}
//创建实例
private static Single instance=null;
//返回实例
public static Single getInstance(){
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if (instance==null){
synchronized(Single.class){
//抢到锁之后再次判断是否为null
if (instance==null){
return instance=new Single();
}
}
}
return instance;
}
}
还是可以破坏
public class test {
public static void main(String[] args) throws Exception {
//获取Single类的私有无参构造方法对象
Constructor constructor = Single.class.getDeclaredConstructor();
//取消访问检查
constructor.setAccessible(true);
Single s1 = (Single) constructor.newInstance();
Field flag = Single.class.getDeclaredField("flag");
flag.setAccessible(true);
//创建一个对象后重新设置flag的值为true
flag.set(s1,false);
Single s2 = (Single) constructor.newInstance();
//判断通过反射创建的两个对象是否是同一个对象
System.out.println(s1);
System.out.println(s2);
}
}
反射不能破坏枚举的单例