单例模式
单例模式的优点:
1.由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
2.单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
常见的五种单例模式实现方式:
主要:
- 饿汉式:线程安全,调用效率高。但是,不能延时加载。
- 懒汉式:线程安全,调用效率不高。但是,可以延时加载。
其他:
- 双重检测锁式:由于JVM底层内部模型原因,偶尔会出现问题,不建议使用。
- 静态内部类式:线程安全,调用效率高,但是可以延时加载。
- 枚举式:线程安全,调用效率高,不能延时加载,并且可以天然的防止反射和反序列化漏洞。
在选用时:
单例对象,占用资源少,不要延时加载时
- 枚举式比饿汉式好。
单例对象,占用资源大,需要延时加载
- 静态内部类式比懒汉式好。
测试五种单例模式
package com.kevin.创建型模式.单例模式;
/**
* @author kevin
* @version 1.0
* @description 测试单例模式
* @createDate 2019/1/18
*/
public class Test {
public static void main(String[] args) {
// 不管创建多少次对象,都是指向同一个对象
SingletonDemo1 s1 = SingletonDemo1.getInstance();
SingletonDemo1 s11 = SingletonDemo1.getInstance();
System.out.println("饿汉式是否是同一个对象: " + (s1 == s11));
SingletonDemo2 s2 = SingletonDemo2.getInstance();
SingletonDemo2 s22 = SingletonDemo2.getInstance();
System.out.println("懒汉式是否是同一个对象: " + (s2 == s22));
SingletonDemo3 s3 = SingletonDemo3.getInstance();
SingletonDemo3 s33 = SingletonDemo3.getInstance();
System.out.println("双重检测锁式是否是同一个对象: " + (s3 == s33));
SingletonDemo4 s4 = SingletonDemo4.getInstance();
SingletonDemo4 s44 = SingletonDemo4.getInstance();
System.out.println("静态内部类式是否是同一个对象: " + (s4 == s44));
SingletonDemo5 s5 = SingletonDemo5.INSTANCE;
SingletonDemo5 s55 = SingletonDemo5.INSTANCE;
System.out.println("枚举式是否是同一个对象: " + (s5 == s55));
}
}
-
饿汉式模式(单例对象立即加载)
package com.kevin.创建型模式.单例模式;
/**
* @author kevin
* @version 1.0
* @description 饿汉式单例模式
* 线程安全,调用效率高。但是,不能延时加载。
* @createDate 2019/2/17
*/
public class SingletonDemo1 {
// 类初始化时,立即加载这个对象(没有延时加载的优势)。由于加载类时,天然的是线程安全的
private static SingletonDemo1 instance = new SingletonDemo1();
public SingletonDemo1() {
}
// 方法没有同步,调用效率高
public static SingletonDemo1 getInstance(){
return instance;
}
}
注:饿汉式单例模式,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。
问题:如果只是加载本类,而不是调用getInstance(),甚至永远没有调用,则会造成资源浪费。
-
懒汉式模式(单例对象延迟加载)
package com.kevin.创建型模式.单例模式;
/**
* @author kevin
* @version 1.0
* @description 懒汉式单例模式
* 线程安全,调用效率不高,但是可以延时加载
* @createDate 2019/2/17
*/
public class SingletonDemo2 {
// 类初始化时,不初始化这个对象(有延时加载的优势,到真正使用的时候再创建)。由于加载类时,天然的是线程安全的
private static SingletonDemo2 instance;
private SingletonDemo2(){
}
// 方法需要同步,效率低
public static synchronized SingletonDemo2 getInstance() {
if(instance == null) {
instance = new SingletonDemo2();
}
return instance;
}
}
注:lazy load。延迟加载,懒加载。真正使用的时候才会加载。
问题:资源利用高了。但是,每次调用getInstance()方法都需要同步,并发效率较低。
双重检测锁实现模式
package com.kevin.创建型模式.单例模式;
/**
* @author kevin
* @version 1.0
* @description 双重检测锁模式
* 由于JVM底层内部模型原因,偶尔会出现问题,不建议使用
* @createDate 2019/2/17
*/
public class SingletonDemo3 {
// 类初始化时,不初始化这个对象,赋值为null,到使用的时候创建对象
private static SingletonDemo3 instance = null;
private SingletonDemo3() {
}
// 调用时,会先判断使用已经同步了对象
public static SingletonDemo3 getInstance() {
if(instance == null) {
SingletonDemo3 sc;
synchronized (SingletonDemo3.class){
sc = instance;
if(sc == null) {
synchronized (SingletonDemo3.class) {
if (sc == null){
sc = new SingletonDemo3();
}
}
instance = sc;
}
}
}
return instance;
}
}
注:这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。
问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出现问题,不建议使用。
静态内部类实现模式(懒加载方式)
package com.kevin.创建型模式.单例模式;
/**
* @author kevin
* @version 1.0
* @description 静态内部类模式
* 线程安全,调用效率高,但是,可以延时加载
* @createDate 2019/2/17
*/
public class SingletonDemo4 {
private SingletonDemo4() {
}
// 将加载对象放在静态内部类中,所以不会立即加载对象
private static class SingletonClassInstance{
private static final SingletonDemo4 instance = new SingletonDemo4();
}
// 只有真正调用getInstance()时才会加载静态内部类,加载类时是线程安全的。
// instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证线程安全
public static SingletonDemo4 getInstance() {
return SingletonClassInstance.instance;
}
}
注:
1.外部类没有static属性,则不会想饿汉式那样立即加载对象。
2.只有真正调用getInstance(),才会加载静态内部类,加载类时是线程安全的。instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
3.兼备了并发高效调用和延迟加载的优势。
枚举式模式
package com.kevin.创建型模式.单例模式;
/**
* @author kevin
* @version 1.0
* @description 枚举单例模式
* 线程安全,调用效率高,不能延时加载
* @createDate 2019/2/17
*/
public enum SingletonDemo5 {
// 枚举元素,本身就是单例对象,但是没有延时加载。定义一个枚举的元素,它就代表了Singleton的一个实例
INSTANCE;
// 单例可以有自己的操作
public void singletonOperation(){
}
}
注:实现简单,枚举本身就是单例模式。由JVM从根本上提供保障,避免通过反射和反序列化的漏洞。
问题:无延迟加载。
1.反射可以破解上面几种(不包含枚举式)实现方式
(可以在构造方法中手动抛出异常控制)
package com.kevin.创建型模式.单例模式;
import java.lang.reflect.Constructor;
/**
* @author kevin
* @version 1.0
* @description 测试反射破解单例模式
* @createDate 2019/2/17
*/
public class Test2 {
public static void main(String[] args) throws Exception {
// 不管创建多少次对象,都是指向同一个对象
SingletonDemo6 s1 = SingletonDemo6.getInstance();
SingletonDemo6 s2 = SingletonDemo6.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println("---------------");
// 通过反射破解单例模式
Class<SingletonDemo6> clazz = (Class<SingletonDemo6>) Class.forName("com.kevin.创建型模式.单例模式.SingletonDemo6");
Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null); //获取构造器
c.setAccessible(true); // 设置为true可以访问private对象
SingletonDemo6 s3 = c.newInstance();
SingletonDemo6 s4 = c.newInstance();
System.out.println(s3);
System.out.println(s4);
}
}
package com.kevin.创建型模式.单例模式;
import java.io.Serializable;
/**
* @author kevin
* @version 1.0
* @description 懒汉式单例模式(测试如何防止反射漏洞)
* @createDate 2019/2/17
*/
public class SingletonDemo6 implements Serializable {
private static SingletonDemo6 instance;
// 手动抛出异常,避免通过反射创建多个单例对象
private SingletonDemo6() throws Exception {
if(instance != null) {
throw new Exception("只能创建一个对象");
}
}
public static synchronized SingletonDemo6 getInstance() throws Exception {
if(instance == null) {
instance = new SingletonDemo6();
}
return instance;
}
}
2.反序列化可以破解上面几种(不包含枚举式)实现方式。
可以通过定义readResolve()防止获得不同对象。
反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。
package com.kevin.创建型模式.单例模式;
import java.io.*;
/**
* @author kevin
* @version 1.0
* @description 测试反序列化破解单例模式
* @createDate 2019/2/17
*/
public class Test2 {
public static void main(String[] args) throws Exception {
// 不管创建多少次对象,都是指向同一个对象
SingletonDemo6 s1 = SingletonDemo6.getInstance();
SingletonDemo6 s2 = SingletonDemo6.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println("---------------");
// 通过反序列化破解单例模式
FileOutputStream fos = new FileOutputStream("D:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/a.txt"));
SingletonDemo6 s5 = (SingletonDemo6) ois.readObject();
System.out.println(s5);
}
}
package com.kevin.创建型模式.单例模式;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* @author kevin
* @version 1.0
* @description 懒汉式单例模式(测试如何防止反序列化漏洞)
* @createDate 2019/2/17
*/
public class SingletonDemo6 implements Serializable {
private static SingletonDemo6 instance;
private SingletonDemo6(){
}
public static synchronized SingletonDemo6 getInstance() throws Exception {
if(instance == null) {
instance = new SingletonDemo6();
}
return instance;
}
// 反序列化时,如果对象所在类定义了readResolve(),(实际上是一种回调),定义返回哪个对象
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
效率测试
常见的五种单例模式在多线程环境下的效率测试,相对值:
测试5种单例模式的效率
package com.kevin.创建型模式.单例模式;
import java.util.concurrent.CountDownLatch;
/**
* @author kevin
* @version 1.0
* @description 测试5种单例模式的效率
* @createDate 2019/2/17
*/
public class Test3 {
public static void main(String[] args) throws Exception {
test1();
test2();
test3();
test4();
test5();
}
public static void test1() throws Exception {
long start = System.currentTimeMillis();
int threadNum = 10; // 线程数
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
Object o = SingletonDemo1.getInstance();
}
countDownLatch.countDown(); // 每次线程执行完将计数器减一
}
}).start();
}
countDownLatch.await(); // main线程阻塞,直到计数器变为0,才会继续往下执行
long end = System.currentTimeMillis();
System.out.println("饿汉式总耗时: " + (end-start));
}
public static void test2() throws Exception {
long start = System.currentTimeMillis();
int threadNum = 10; // 线程数
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
Object o = SingletonDemo2.getInstance();
}
countDownLatch.countDown(); // 每次线程执行完将计数器减一
}
}).start();
}
countDownLatch.await(); // main线程阻塞,直到计数器变为0,才会继续往下执行
long end = System.currentTimeMillis();
System.out.println("懒汉式总耗时: " + (end-start));
}
public static void test3() throws Exception {
long start = System.currentTimeMillis();
int threadNum = 10; // 线程数
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
Object o = SingletonDemo3.getInstance();
}
countDownLatch.countDown(); // 每次线程执行完将计数器减一
}
}).start();
}
countDownLatch.await(); // main线程阻塞,直到计数器变为0,才会继续往下执行
long end = System.currentTimeMillis();
System.out.println("双重检测锁式总耗时: " + (end-start));
}
public static void test4() throws Exception {
long start = System.currentTimeMillis();
int threadNum = 10; // 线程数
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
Object o = SingletonDemo4.getInstance();
}
countDownLatch.countDown(); // 每次线程执行完将计数器减一
}
}).start();
}
countDownLatch.await(); // main线程阻塞,直到计数器变为0,才会继续往下执行
long end = System.currentTimeMillis();
System.out.println("静态内部类式总耗时: " + (end-start));
}
public static void test5() throws Exception {
long start = System.currentTimeMillis();
int threadNum = 10; // 线程数
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
Object o = SingletonDemo5.INSTANCE;
}
countDownLatch.countDown(); // 每次线程执行完将计数器减一
}
}).start();
}
countDownLatch.await(); // main线程阻塞,直到计数器变为0,才会继续往下执行
long end = System.currentTimeMillis();
System.out.println("枚举式总耗时: " + (end-start));
}
}
CountDownLatch
同步辅助类,在完成一组正在其它线程中执行的操作前,它允许一个或多个线程一直等待。
countDown()当前线程调用此方法,则计数减一(建议放在finally里执行)
await(),调用此方法会一直阻塞当前线程,知道计时器的值为0