引言:更多相关请看 Java其它
概述:保证一个类中只有一个实例、提供对外访问的方法。
组成:私有化构造方法、私有静态属性、公共静态方法(获取对象)。
应用场景:Servlet、日志管理、数据库连接池、Window系统的任务浏览器。
优点:应用启动直接创建一个对象长驻内存,减少系统性能开销。
分类:
饿汉式:线程安全、效率高、不能延时加载。
懒汉式:线程安全、效率低、能延时加载(lazy load)。
双重检测锁模式:因为编译器优化和JVM底层内部模型原理,偶尔出现问题,不建议使用。
静态内部类式:线程安全、效率高、能延时加载。
枚举单例:线程安全、效率高、不能延时加载。
适用场景:单例对象占用资源小、无需延迟加载(单例好于饿汉式),单例对象占用资源大、需要延迟加载(静态内部类好于懒汉式)。
饿汉式
概述:线程安全、效率高、不能延时加载。一开始就创建对象,天然安全,不需要加同步。
示例:
class TestSingleton {
//类初始化时马上加载对象
private static volatile TestSingleton instance = new TestSingleton();
//私有化构造
private TestSingleton(){}
//对外访问方式 不需要加同步,类初始化加天然安全
public static TestSingleton getInstance(){
return instance;
}
public static void main(String[] args) {
System.out.println(TestSingleton.getInstance());
System.out.println(TestSingleton.getInstance());
}
}
懒汉式
概述:线程安全、效率低、能延时加载(lazy load)。等需要时再创建对象,加synchronized保证安全(避免重复创建对象)。
示例:
class TestSingleton {
//私有静态属性
private static volatile TestSingleton instance;
//私有构造
private TestSingleton() {
}
//同步公共静态方法 加同步避免重复创建对象
public static synchronized TestSingleton getInstance() {
if (instance == null) {//如果为空,才让它创建
instance = new TestSingleton();//需要对象时才创建
}
//返回对象
return instance;
}
public static void main(String[] args) {
System.out.println(TestSingleton.getInstance());
System.out.println(TestSingleton.getInstance());
}
}
双重检测锁式
概述:DCL Double Checking Locking。因为编译器优化和JVM底层内部模型原理,偶尔出现问题,不建议使用。把同步(可重入锁)放到方法里面,保证对象只有第一次创建的时候才需要同步,提高性能。
示例:
class TestSingleton {
private volatile static TestSingleton instance;
private TestSingleton() {
}
public static TestSingleton getInstance() {
// 第一次检测
if (instance == null) {
synchronized (TestSingleton.class) {
// 第二次检测
if (instance == null) {
instance = new TestSingleton();
}
}
}
return instance;
}
public static void main(String[] args) {
System.out.println(TestSingleton.getInstance());
System.out.println(TestSingleton.getInstance());
}
}
静态内部类式
概述:线程安全、效率高、能延时加载。静态内部类属性是对对外部类的引用(实例在内部类创建),内部类的属性加static、final,保证对象只有一个,只能被赋值一次。
示例:
class TestSingleton {
//静态内部类
private static class SingleClass {
//建立对外部类的引用 加静态私有保证只有一个对象,只能被赋值一次
private static final TestSingleton instance = new TestSingleton();
}
//私有构造
private TestSingleton() {
}
//提供公共访问方式
public static TestSingleton getInstance() {
return SingleClass.instance;
}
public static void main(String[] args) {
System.out.println(TestSingleton.getInstance());
System.out.println(TestSingleton.getInstance());
}
}
枚举式
概述:线程安全、效率高、不能延时加载。枚举本身就是一个单例,JVM从根本保证反射和反序列化的漏洞。缺点是不能延时加载。
注意:枚举属性默认修饰符public static final。
示例:
public enum TestSingleTon {
instance;//这个枚举元素本身就是单例对象
}
class TestEnum {
public static void main(String[] args) {
//测试
System.out.println(TestSingleTon.instance == TestSingleTon.instance);//true
}
}
反射和反序列化漏洞(除了枚举)
单例有漏洞(除枚举外),可通过反射和反序列化可能造成对象不只有一个。
反射漏洞
以懒汉式演示漏洞:
public class TestSingleton {
//私有静态属性
private static TestSingleton instance;
//私有构造
private TestSingleton() {
}
//同步公共静态方法 加同步避免重复创建对象
public static synchronized TestSingleton getInstance() {
if (instance == null) {//如果为空,才让它创建
instance = new TestSingleton();//需要对象时才创建
}
//返回对象
return instance;
}
// 编写反射漏洞测试:
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
TestSingleton a = TestSingleton.getInstance();
TestSingleton b = TestSingleton.getInstance();
System.out.println("静态方法获取对象:" + a);
System.out.println("静态方法获取对象:" + b);
System.out.println("====================");
// 反射破解
Class<TestSingleton> clazz = (Class<TestSingleton>) Class.forName("com.example.demo.test01.TestSingleton");
Constructor<TestSingleton> cons = clazz.getDeclaredConstructor();
cons.setAccessible(true);// 获取私有方法
TestSingleton c = cons.newInstance();
TestSingleton d = cons.newInstance();
System.out.println("反射创建对象:" + c);
System.out.println("反射创建对象:" + d);
}
}
反射创建对象的结果不一致:
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
====================
反射创建对象:com.example.demo.test01.TestSingleton@19469ea2
反射创建对象:com.example.demo.test01.TestSingleton@13221655
解决:在懒汉式类里面添加(如果对象不为空,想创建对象就会抛出异常)
//私有构造
private TestSingleton() {
if (instance != null) {
throw new RuntimeException();// 防止反射漏洞,避免通过构造方法创建对象
}
}
完整代码:
public class TestSingleton {
//私有静态属性
private static TestSingleton instance;
//私有构造
private TestSingleton() {
if (instance != null) {
throw new RuntimeException();// 防止反射漏洞,避免通过构造方法创建对象
}
}
//同步公共静态方法 加同步避免重复创建对象
public static synchronized TestSingleton getInstance() {
if (instance == null) {//如果为空,才让它创建
instance = new TestSingleton();//需要对象时才创建
}
//返回对象
return instance;
}
// 编写反射漏洞测试:
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
TestSingleton a = TestSingleton.getInstance();
TestSingleton b = TestSingleton.getInstance();
System.out.println("静态方法获取对象:" + a);
System.out.println("静态方法获取对象:" + b);
System.out.println("====================");
// 反射破解
Class<TestSingleton> clazz = (Class<TestSingleton>) Class.forName("com.example.demo.test01.TestSingleton");
Constructor<TestSingleton> cons = clazz.getDeclaredConstructor();
cons.setAccessible(true);// 获取私有方法
TestSingleton c = cons.newInstance();
TestSingleton d = cons.newInstance();
System.out.println("反射创建对象:" + c);
System.out.println("反射创建对象:" + d);
}
}
效果:(不让通过构造方法创建对象,直接抛出异常)
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
====================
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.example.demo.test01.TestSingleton.main(TestSingleton.java:38)
Caused by: java.lang.RuntimeException
at com.example.demo.test01.TestSingleton.<init>(TestSingleton.java:13)
... 5 more
反序列化漏洞
测试反序列化:(先给懒汉式类加了实现序列化接口)
// 实现序列化接为了将对象序列化
public class TestSingleton implements Serializable {
//私有静态属性
private static TestSingleton instance;
//私有构造
private TestSingleton() {
}
//同步公共静态方法 加同步避免重复创建对象
public static synchronized TestSingleton getInstance() {
if (instance == null) {//如果为空,才让它创建
instance = new TestSingleton();//需要对象时才创建
}
//返回对象
return instance;
}
// 编写反序列化漏洞测试:
public static void main(String[] args) throws Exception {
TestSingleton a = TestSingleton.getInstance();
TestSingleton b = TestSingleton.getInstance();
System.out.println("静态方法获取对象:" + a);
System.out.println("静态方法获取对象:" + b);
System.out.println("====================");
// 反序列化破解
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
oos.writeObject(TestSingleton.getInstance());// 序列化
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
// 反序列化
Object c = ois.readObject();
System.out.println("反序列化创建对象:" + c);
ois.close();
}
}
效果:
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
====================
反序列化创建对象:com.example.demo.test01.TestSingleton@17f052a3
解决(在懒汉式类里添加readResolve方法):
// 防止反序列化漏洞
public Object readResolve() {
return instance;
}
再来看结果(就是一个对象):
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
静态方法获取对象:com.example.demo.test01.TestSingleton@3af49f1c
====================
反序列化创建对象:com.example.demo.test01.TestSingleton@3af49f1c
注意:个人推测是因为ois.readObject()底层会调用readResolve()去创建对象,所以我们重写readResolve()时会直接调用重写的readResolve()并通过return instance;返回单独的实例。

五种单例的效率对比
优先看测试结果:

测试示例:
/**
* 懒汉式
*/
class TestSingleton1 {
//类初始化时马上加载对象
private volatile static TestSingleton1 instance = new TestSingleton1();
//私有化构造
private TestSingleton1() {
}
//对外访问方式 不需要加同步,类初始化加天然安全
public static TestSingleton1 getInstance() {
return instance;
}
}
/**
* 懒汉式
*/
class TestSingleton2 {
//私有静态属性
private volatile static TestSingleton2 instance;
//私有构造
private TestSingleton2() {
}
//同步公共静态方法 加同步避免重复创建对象
public static synchronized TestSingleton2 getInstance() {
if (instance == null) {//如果为空,才让它创建
instance = new TestSingleton2();//需要对象时才创建
}
//返回对象
return instance;
}
}
/**
* 双重检测锁式DCL Double Checking Lock
*/
class TestSingleton3 {
private volatile static TestSingleton3 instance;
private TestSingleton3() {
}
public static TestSingleton3 getInstance() {
// 第一次检测
if (instance == null) {
synchronized (TestSingleton3.class) {
// 第二次检测
if (instance == null) {
instance = new TestSingleton3();
}
}
}
return instance;
}
}
/**
* 静态内部类式
*/
class TestSingleton4 {
//静态内部类
private static class SingleClass {
//建立对外部类的引用 加静态私有保证只有一个对象,只能被赋值一次
private static final TestSingleton4 instance = new TestSingleton4();
}
//私有构造
private TestSingleton4() {
}
//提供公共访问方式
public static TestSingleton4 getInstance() {
return SingleClass.instance;
}
}
enum TestSingleTon5 {
instance;//这个枚举元素本身就是单例对象
}
public class TestSLT {
public static void main(String[] args) throws InterruptedException {
//开始
long start = System.currentTimeMillis();
//同步辅助类 ,可以让一个或者多个线程等待一组操作完成之后执行
final CountDownLatch count = new CountDownLatch(10);//
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000000; j++) {
// Object o = TestSingleton1.getInstance();//饿汉式 时间:24
// Object o = TestSingleton2.getInstance();//懒汉式 时间:430
// Object o = TestSingleton3.getInstance();//双重检测式 时间:27
// Object o = TestSingleton4.getInstance();//静态内部类式 时间:36
Object o = TestSingleTon5.instance;//枚举式 时间:18
}
count.countDown();//计数减一
}
}).start();
}
count.await();//必须等线程操作完成,main线程才能开始
long end = System.currentTimeMillis();//结束
System.out.println("花费时间:" + (end - start));
}
}