uRick's PKM uRick's PKM
首页
导航
  • Java
  • 数据库
书单
  • 优质好文
  • 有趣的工具
  • 分类
  • 标签
  • 归档
关于
首页
导航
  • Java
  • 数据库
书单
  • 优质好文
  • 有趣的工具
  • 分类
  • 标签
  • 归档
关于
  • 一文搞懂JVM知识
  • 多线程基础
  • JUC☞Thread Pool
  • JUC☞Locker
  • JUC☞Queue
  • NIO浅谈
  • 有趣的二进制
  • 深入理解Lambda
  • Java8新特性
  • 单实例多种实现与解析
    • 1. 立即加载/饿汉式
    • 2. 延迟加载/懒汉式
    • 3. 静态内部类和静态代码块实现
    • 4. 反射方式实现
    • 5. 枚举方式实现
    • 6. ThreadLocal方式实现
    • 7. 什么情况单实例会被破坏
  • java
uRick
2019-12-20
目录

单实例多种实现与解析

# 1. 立即加载/饿汉式

立即加载通过静态成员变量特性初始化,直接通过暴露方法getInstance()返回实例,即暴力又粗鲁,所以称为饿汉式单实例;
这种方式是绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题。 优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。 缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存。

public class Singleton {
    private static Singleton singleton = new Singleton();
    public static Singleton getInstance() {
        return singleton;
    }
    private Singleton() {}
}
1
2
3
4
5
6
7

# 2. 延迟加载/懒汉式

延迟加载即是在使用实例的时候创建,也俗称饿汉式单实例

  1. 错误的实现方式

在多线程环境下,创建的实例可能会是多个,不安全。

public class Singleton {
    private static Singleton singleton;
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
    private Singleton() {}
}
1
2
3
4
5
6
7
8
9
10
  1. 多线安全方式
    • 加锁方式

    使用synchronized方式实现多线安全的单实例,由于synchronized重量级锁,当访问线程数增加后,CPU压力增大,程序运行性能就会变低,所以效率上比较低

同步方法机制

/**
* 多线程安全
*/
public class Singleton {
    private static Singleton singleton;
    public synchronized static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
    private Singleton() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

同步代码块机制

/**
* 多线程安全,同synchronized方法实现方式一样
*/
public class Singleton {
    private static Singleton singleton;
    public static Singleton getInstance() {
        synchronized(Singleton.class){
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        return singleton;
    }
    private Singleton() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

同步实例初始化机制

/**
* 非多线程安全,只是同步了实例化代码
*/
public class Singleton {
    private static Singleton singleton;
    public static Singleton getInstance() {
            if (singleton == null) {
                synchronized(Singleton.class){
                    singleton = new Singleton();
                }
            }
        return singleton;
    }
    private Singleton() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

DCL机制

/**
* 使用DCL(double Check Lock)检查机制实现线程安全,业界俗称双重检查机制
*/
public class Singleton {
    private static Singleton singleton;
    public static Singleton getInstance() {
     if (singleton == null) {//第一次检查位置A
            synchronized (Singleton.class) {
                if (singleton == null) {// 第二次检查
                        //如下代码,singleton初始化时可能被优化,
                        //重排序出现中间状态,导致在位置A检查时,singleton实例并未被真正初始化.
                        singleton = new Singleton();
                   }
            }
        }
        return singleton;
    }
    private Singleton() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

DCL-volatil机制

使用volatile修饰,保证了变量多线程的可见性,也限制的重排序,所以更安全。

/**
* 使用DCL(double Check Lock)检查机制实现线程安全,业界俗称双重检查机制
*/
public class Singleton {
    private volatile static  Singleton singleton;
    public static Singleton getInstance() {
     if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                        singleton = new Singleton();//禁止指令重排
                    }
            }
        }
        return singleton;
    }
    private Singleton() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 3. 静态内部类和静态代码块实现

两种实现方式都是利用了static 修饰不,保证类被加载时就已经被初始化,所以多线程环境下也是安全的。

静态内部类

public class Singleton {
    private static class SingletonHandler {
        private static Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHandler.instance;
    }
    private Singleton() {}
}
1
2
3
4
5
6
7
8
9

静态代码块

public class Singleton  {
    private static Singleton singleton = null;
    static {
        singleton = new Singleton();
    }
    private Singleton() {}
public static Singleton getInstance() {
        return singleton;
    }
}
1
2
3
4
5
6
7
8
9
10

# 4. 反射方式实现

为了使用反射机制实现单实例,需要结合静态内部类方式保证反序列时对象是一个。 注意,反射可能会破坏单实例,所有需要做一些限制。

public class Singleton  implements Serializable {
    private static class SingletonHandler {
            private static Singleton instance = new Singleton();
        }
        public static Singleton getInstance() throws InterruptedException {
            return SingletonHandler.instance;
        }
        private Singleton() {}

    /**
    * 序列化
    */
     public static void serSingleton() throws InterruptedException, IOException {
            Singleton instance = Singleton.getInstance();
            FileOutputStream fileOutputStream = new FileOutputStream("singleton.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream);
            oos.writeObject(instance);
            System.out.println(instance.hashCode());
            fileOutputStream.close();
            oos.close();
       }

    /**
    * 反序列化
    */
    public static void derSingleton() throws InterruptedException, IOException, ClassNotFoundException {
        FileInputStream fileInputStream = new FileInputStream("singleton.txt");
        ObjectInputStream ois = new ObjectInputStream(fileInputStream);
        // 新实例
        Singleton o = (Singleton) ois.readObject();
        // 基于对象调用静态内部类方法获取实例(线程安全)
        Singleton Singleton = o.resolveSingleton();
        System.out.println(Singleton.hashCode());
        fileInputStream.close();
        ois.close();
      }
    /**
     * 结合静态内部类实现
     */
     protected Singleton resolveSingleton(){
            return SingletonHandler.instance;
      }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 5. 枚举方式实现

使用枚举初始化特性实现单实例,使用枚举实例时都会调用默认的无参构造,使用枚举能够避免单实例被破坏,不管是使用反射或者序列化,是因为JDK内部机制决定它只能实例化一次,实例化多次会抛出异常。

public enum Singleton {
    SINGLETON;
    private String singleton;
    private Singleton(){
        singleton = new String("singleton");
    }
    public String getInstance(){
        return singleton;
    }
}
1
2
3
4
5
6
7
8
9
10

# 6. ThreadLocal方式实现

这种方式无论单实例被线程调用多少次都会创建当前线程唯一的单实例,因为线程实例都存在ThreadLocalMap中的,能够保证为每个线程提供唯一的对象实例。

public class Singleton {
    private static final ThreadLocal<Singleton> signleton = new ThreadLocal<Singleton>() {
        @Override
        protected Singleton initialValue() {
            return new Singleton();
        }
    };
    private Singleton() {}

    public static Singleton getInstance() {
        return signleton.get();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 7. 什么情况单实例会被破坏

  1. 反射方式:对于单实例,通过反射方式是可以被实例化多次的,可以在单实例构造方法中加以约束,实现防反射破坏。
  2. 反序列化:反序列化与反射方式都是类似的,本身也是反射方式实例化;反序列化可以通过添加构造函数约束和readResolve()方法,注意readResolve()是JDK的ObjectInputStream的一种机制,通过反射方式找到readResolve()解析实例,但是这种方式会被实例化多次的,只是不会返回新创建的实例而已。
#设计模式#单例
上次更新: 2023/01/18, 02:33:51
Java8新特性

← Java8新特性

最近更新
01
从0到1:开启商业与未来的秘密
11-26
02
如何阅读一本书: 读懂一本书,精于一件事
10-25
03
深入理解Lambda
06-27
更多文章>
Theme by Vdoing | Copyright © 2019-2024 uRick | CC BY 4.0
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式