共计 5887 个字符,预计需要花费 15 分钟才能阅读完成。
文章目录
Java 单例模式
单例模式是 Java 中最简略的设计模式之一。这种类型的设计模式属于创立型模式,它提供了一种创立 对象的最佳形式
单例模式确保在一个应用程序中某一个类只有一个实例,而且自行实例化并向整个零碎提供这个实例 单例实例。
满足条件
单例模式只应在有真正的“繁多实例”的需要时才可应用:
- 单例类只能有一个实例
- 单例类必须本人创立本人的惟一实例
- 单例类必须给所有其余对象提供这一实例
两种模式
Java 中实现单例模式能够通过两种模式实现:
- 懒汉模式 (类加载时不初始化)
- 饿汉模式 (在类加载时就实现了初始化,所以类加载较慢,但获取对象的速度快)
设计要求
编写单例必须满足上面的条件:
- 构造方法变成公有
- 提供一个静态方法获取单实例对象
饿汉模式
饿汉模式基于 classloader 机制防止了多线程的同步问题 (动态初始化将保障在任何线程可能拜访到域之前初始化它),不过,instance 在类装载时就实例化,这时候初始化 instance 显然没有达到懒加载(lazy loading)的成果
饿汉单例绝对比拟容易了解,个别体现为以下两种模式:
package com.shixun.design.singleton;
public class Singleton1 {private static Singleton1 instance = new Singleton1();
// 公有构造方法,保障外界无奈间接实例化。private Singleton1() {}
// 通过私有的静态方法获取对象实例
public static Singleton1 getInstance() {return instance;}
}
也能够将动态对象初始化放在动态代码块中
package com.shixun.design.singleton;
public class Singleton2 {
private static Singleton2 instance = null;
// 对象初始化放在动态代码块中
static {instance = new Singleton2();
}
// 公有构造方法,保障外界无奈间接实例化。private Singleton2() {}
// 通过私有的静态方法获取对象实例
public static Singleton2 getInstance() {return instance;}
}
懒汉形式
实现单例模式可能进步类加载性能,然而和饿汉模式借助与 JVM 的类加载外部同步机制实现了线程平安不同,须要在提早加载时留神单例实例的线程安全性,如果简略粗犷的实现,在多线程环境中将引起运行异样。
例如上面代码将引起运行异样:
package com.shixun.design.singleton;
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {}
public static Singleton3 getInstance() {if (instance == null) {instance = new Singleton3();
}
return instance;
}
}
上述代码多线程同时拜访时可能会产生多个示例,甚至会毁坏实例,违反单例的设计准则
用上面代码也能测试进去:
package com.shixun.design.singleton;
public class SingletonTest implements Runnable{
@Override
public void run() {Singleton3 singleton3 =Singleton3.getInstance();
System.out.println(singleton3);
}
public static void main(String[] args) {for (int i=0;i<10;i++){SingletonTest myThread = new SingletonTest();
Thread thread = new Thread(myThread, String.valueOf(i));
Thread thread2 = new Thread(myThread, String.valueOf(i));
Thread thread3 = new Thread(myThread, String.valueOf(i));
thread.start();
thread2.start();
thread3.start();}
}
}
懒汉式多线程解决方案
synchronized
能够为返回单例实例的办法设置同步用来保障线程安全性
package com.shixun.design.singleton;
public class Singleton4 {
private static Singleton4 instance;
private Singleton4() {}
public synchronized static Singleton4 getInstance() {if (instance == null) {instance = new Singleton4();
}
return instance;
}
}
这种写法可能在多线程中很好的工作,而且看起来它也具备很好的懒加载(lazy loading),但遗憾的是,因为整个办法被同步,因而效率绝对较低
双查看锁形式
应用双查看锁须要进行两次 instance == null 的判断
- 第一次判断没有锁,如果 install 不为 null 间接返回单实例对象,提高效率
- 第二次判断避免多线程创立多个实例,如果 A 和 B 两个线程同时争抢 synchronized 锁,A 先争抢到锁,B 期待,A 线程 instance 赋值实例化对象,开释锁,B 线程获取到到锁,如果没有第二次判断的话,间接又会创建对象,那么就不合乎单例要求
并且还须要为这个动态对象加上 volatile 关键字,volatile 在这里的作用是:告诉其余线程及时更新变量;保障有序性,禁止指令重排序。
告诉其余线程及时更新变量还简单明了,第二个作用是这样的,咱们举例说明一下:
起因举例说明: 在执行 instance = new Singleton() 语句时,一共是有三步操作的。
- 堆中分配内存
- 调用构造方法进行初始化
- 将 instance 援用指向内存地址。
在这三步有可能会产生指令重排序即有两种后果可能产生:123 与 132(不管怎么重排序,单线程程序 的执行后果不会扭转)
如果 A 线程执行到 instance = new Singleton()
,此时 2,3 产生重排序,选执行 3,则 instance 曾经不为 null,然而指向的对象还未初始化实现,如果此时 B 对象判断 instance 不为 null 就会间接返回一个未初始 化实现的对象。
双查看锁形式代码如下:
package com.shixun.design.singleton;
public class Singleton5 {
private volatile static Singleton5 instance;
private Singleton5() {}
// 应用双查看锁形式
public static Singleton5 getInstance() {if (instance == null) {synchronized(Singleton5.class){if(instance == null){instance = new Singleton5();
}
}
}
return instance;
}
}
再测一下,没有问题:
package com.shixun.design.singleton;
public class SingletonTest implements Runnable{
@Override
public void run() {Singleton5 singleton5 =Singleton5.getInstance();
System.out.println(singleton5);
}
public static void main(String[] args) {for (int i=0;i<10;i++){SingletonTest myThread = new SingletonTest();
Thread thread = new Thread(myThread, String.valueOf(i));
Thread thread2 = new Thread(myThread, String.valueOf(i));
Thread thread3 = new Thread(myThread, String.valueOf(i));
Thread thread4 = new Thread(myThread, String.valueOf(i));
Thread thread5 = new Thread(myThread, String.valueOf(i));
thread.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();}
}
}
动态外部类
之前提到了,动态初始化将在实例被任何线程拜访到之前对其进行初始化,因而,能够借助于这个个性对懒汉单例进行革新:
动态外部类加载机制:应用时候才被加载,而且多线程状况下,classloader 可能保障只加载一份字节码
代码如下:
package com.shixun.design.singleton;
public class Singleton6 {
private static class SingletonHolder {private final static Singleton6 Instance = new Singleton6();
}
private Singleton6() {}
public static final Singleton6 getInstance() {return SingletonHolder.Instance;}
public void say() {System.out.println("调用了 say 办法");
}
public static void sayHello() {System.out.println("调用了 sayHello 办法");
}
测试也 OK:
package com.shixun.design.singleton;
public class SingletonTest implements Runnable{
@Override
public void run() {Singleton6 singleton6 =Singleton6.getInstance();
System.out.println(singleton6);
singleton6.say();}
public static void main(String[] args) {for (int i=0;i<10;i++){SingletonTest myThread = new SingletonTest();
Thread thread = new Thread(myThread, String.valueOf(i));
Thread thread2 = new Thread(myThread, String.valueOf(i));
Thread thread3 = new Thread(myThread, String.valueOf(i));
Thread thread4 = new Thread(myThread, String.valueOf(i));
Thread thread5 = new Thread(myThread, String.valueOf(i));
thread.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();}
}
}
枚举(别瞎用)
JDK1.5 之后引入了枚举,因为枚举的个性,能够利用其来实现单例,它不仅能防止多线程同步问 题,而且还能避免反序列化从新创立新的对象 (序列化和反序列化后是同一个对象)
代码如下
package com.shixun.design.singleton;
public enum Singleton7 {
INSTANCE;
public void say(){System.out.println("say ni hello!");
}
}
测试一下:
package com.shixun.design.singleton;
public class SingletonTest implements Runnable{
@Override
public void run() {
Singleton7 singleton7 = Singleton7.INSTANCE;
System.out.println(singleton7.hashCode());
singleton7.say();}
public static void main(String[] args) {for (int i=0;i<10;i++){SingletonTest myThread = new SingletonTest();
Thread thread = new Thread(myThread, String.valueOf(i));
Thread thread2 = new Thread(myThread, String.valueOf(i));
Thread thread3 = new Thread(myThread, String.valueOf(i));
Thread thread4 = new Thread(myThread, String.valueOf(i));
Thread thread5 = new Thread(myThread, String.valueOf(i));
thread.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();}
}
/**
* 生写懒汉式多线程问题
*/
private static void method01() {for (int i=0;i<100;i++){SingletonTest myThread = new SingletonTest();
Thread thread = new Thread(myThread, String.valueOf(i));
Thread thread2 = new Thread(myThread, String.valueOf(i));
Thread thread3 = new Thread(myThread, String.valueOf(i));
thread.start();
thread2.start();
thread3.start();}
}
}