单例设计模式 (Singleton)

描述: 一个类有且仅有一个实例的类

特点: 单例类只有一个实例,单例必须自己创建自己的唯一实例,必须给其他的访问对象提供自己的试列 。这样的好处就是避免了内存的频繁的创建和销毁实例。

饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
  /*饿汉式
类加载到内存之后就一个实例 , jvm保证线程的安全
缺点就是不管用到与否 都会在内存中产生一个实列*/
class Test{
//构造器私有,不能被外界随便调用
private Test(){}
private static final Test t = new Test();

//提供访问接口 只能是 类名.的方式调用
public static Test getTest(){
return t;
}
}

懒汉式(线程不安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  /*懒汉式 
线程不安全*/
class Test {
private static Test t = null;

//构造器私有,不能被外界随便调用
private Test() {
}

//提供访问接口 只能是 类名.的方式调用
public static Test getTest() {
if (t == null) {
//在这里有可能发生线程不安全 假如t1执行到了这,此时cpu被另外的一个线程t2抢走 就会发生在内存中会有两个实例的存在
t = new Test();
}
return t;
}
}

懒汉式/双重检查机制(线程安全的 DCL)

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
  /*懒汉式
线程安全*/
class Test {
private volatile static Test t = null;
//注意 此处的 volatile 用来防止指令重排序的
//当我们创建一个对象的时候 分为3步完成 1.在内存开辟空间 2.调用构造函数初始化成员变量 3.调用对象的<init> 指向内存空间
//在cpu指令的优化下 此时可以看到 2和3 没有 逻辑上的顺序 所以此时有可能 1 3 2顺序执行 就在此时cpu指令执行到了 1 3 // 有一个线程抢走cpu 执行下方的第一个if代码 此时t 已经 !=null 但是成员变量并未初始化 所以报错

//构造器私有,不能被外界随便调用
private Test() {
}

//提供访问接口 只能是 类名.的方式调用
public static Test getTest() {
if (t == null) { //此处的判断是为了考虑到运行的效率,假如此时有100个线程,如果在这里不加判断的情况就是100个线程都 // 会在这里抢锁,消耗资源。
// 加上锁之后 当一个线程执行完之后 t != null 所以就不用抢占资源 ,直接return
synchronized (Test.class) {
if(t== null){
t = new Test();
}
}
}
return t;
}
}

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  /*静态内部类
* jvm帮我们保证线程安全
*jvm帮我们保证单例
*外部类加载时 内部类不会加载(懒加载模式)
* */
class Test {
//构造器私有,不能被外界随便调用
private Test() {
}
private static class Test1{
private final static Test t1 = new Test();
}
//提供访问接口 只能是 类名.的方式调用
public static Test getTest() {
return Test1.t1;
}
}
基于枚举单列模式(推荐使用)
1
2
3
enum  Test {
TEST;
}

注意: 其中只有枚举方式的单例不会被破坏,其他都能够使用反射或者序列化的方式破坏单例

实例: 使用反射破坏单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class test5 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//通过静态方法获取单例
Test test = Test.getTest();
//获取Singleton的无参构造器
Constructor<Test> constructor = Test.class.getDeclaredConstructor();
//因为构造器是私有的,需要设置权限
constructor.setAccessible(true);
//使用构造器创建对象
Test t1 = constructor.newInstance();
//比较是否相等,答案是false
System.out.println(test.equals(t1));
}

}

避免反射破坏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Test{
//构造器私有,不能被外界随便调用
private static final Test t = new Test();
private Test(){
if(t != null){
throw new RuntimeException("请使用getSingleton()方法获取单例对象");
}
}

//提供访问接口 只能是 类名.的方式调用
public static Test getTest(){
return t;
}
}

使用序列化破坏(须实现序列化接口 Serializable)

1
2
3
4
5
6
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
byte[] serialize = SerializationUtils.serialize(instance);
Singleton newInstance = SerializationUtils.deserialize(serialize);
System.out.println(instance == newInstance);
}

避免反序列化破坏 就是不实现反序列化接口