单例模式:保证一个类只有一个实例,并且提供一个访问该类实例的全局访问点。
优点:
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永驻留内存的方式来解决
- 单例模式可以在系统设置全局的访问点,优化共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
单例实现要尽可能实现 线程安全 效率高 懒加载
常见的五种单例模式的实现方式:
主要分类
- 懒汉 (线程安全 调用效率不高,但是可以延时加载)
- 饿汉 (线程安全 调用效率高, 但是不能延时加载)
其他分类
- 双重检测锁式 (由于JVM底部内存模型问题,偶尔会出现问题。不建议使用)
- 静 态 内 部 类(线程安全,调用效率高,但是,不可以延时加载)
- 枚 举 类 型 (线程安全,调用效率高, 不能延时加载)
单例实现
1、饿汉式(单例对象立即加载)
package feng.com.GOF;
/**
* 单例模式实现 饿汉式
* @author Administrator
*
*/
public class SingletonDemo01 {
// 类加载是天然线程安全的的
private static SingletonDemo01 single1 = new SingletonDemo01(); //类初始化时立刻加载这个对象
private SingletonDemo01() {}; // 私有化构造器
public static SingletonDemo01 getInstance() {
return single1;
}
}
static变量会在类加载时初始化,且一个类只会被加载一次,也就保证了single1实例是唯一的。但是如果只是加载本类,不调用getInstance()就会造成浪费,因为不管调用与否,实例single1 都会被创建。 构造器私有化,其他类中获取实例只能通过getInstance()方法获取对象实例。
2、懒汉式(单例对象可延时加载)
package feng.com.GOF;
/**
* 单例实现 懒汉式
* @author feng
*
*/
public class SingletonDemo02 {
private static SingletonDemo02 single2; //未初始化实例对象
private SingletonDemo02() {}; //私有化构造方法
public static synchronized SingletonDemo02 getInstance() {
if(single2 == null) {
single2 = new SingletonDemo02(); //真正使用时才会创建对象
}
return single2;
}
}
懒汉式(懒加载/延时加载),真正调用时再加载实例,延时加载。 但是资源利用率太低了。每次调用getInstance()方法都需要同步(不同步的话,可能多个线程同时调用该方法,导致生成了不止一个实例),并发效率低。
3、双重检测锁机制
网络看到过一个双重锁代码实现
package feng.com.GOF;
public class SingletonDemo03 {
private static SingletonDemo03 single3;
private SingletonDemo03() {};
public static SingletonDemo03 getInstance() {
if(single3 == null) {
SingletonDemo03 tempInstance;
synchronized (SingletonDemo03.class) {
tempInstance = single3;
if(tempInstance == null) {
// 我也不太明白第一个同步锁内 第二个同步锁有什么意义。。。
synchronized (SingletonDemo03.class) {
if(tempInstance == null) {
tempInstance = new SingletonDemo03();
}
}
single3 = tempInstance;
}
}
}
return single3;
}
}
就个人来说,已经加过一个同步锁了,内部的锁还有意义没有,锁学的不是很好,不太明白,感觉是没什么意义。但这代码确实有。。。求大神指教。。。
如果按个人理解,双重检测锁 并不是两个锁的意思吧。。。而是在锁上加两层检测来保证实例只会创建一次。
package feng.com.GOF;
public class SingletonDemo03_1 {
private static SingletonDemo03_1 single3;
private SingletonDemo03_1() {};
public static SingletonDemo03_1 getInstance() {
if(single3 == null) { // 判断实例是否为空 只有第一次加载该实例时会执行下列代码块
synchronized (SingletonDemo03_1.class) { // 加锁
if(single3 == null) { // 二次判断的目的是可能有线程先获取到同步锁。已经生成了实例对象
single3 = new SingletonDemo03_1(); // 生成实例对象
}
}
}
return single3;
}
}
single3是静态变量,放在静态区中,也就意味着该变量是所有线程共有的。这才是进行二次判断保证线程不重复创建实例的依据。 双重检测将同步内容放置到了if内部,提高执行效率,不必每次获取对象时都需同步,只有第一次才同步创建,之后就没不要了。
据说 由于编译器优化和JVM底层内部模型原因,偶尔会出现问题,不建议使用。(咱也不懂,也不知道上哪问。。。会出现什么问题。。。也有说这个方法好的)
4、静态内部类实现方式
静态内部类不会再外部类初始化时初始化,而是调用时再初始化,可以看做顶级类。
package feng.com.GOF;
/**
* 静态内部类实现单例
* @author feng
*
*/
public class SingletonDemo04 {
private SingletonDemo04() {
}
// 特别注意 外部类初始化的时候不会初始化静态内部类。
private static class SingletonClassInstance{
private static final SingletonDemo04 single04 = new SingletonDemo04();
}
public static SingletonDemo04 getInstance() {
return SingletonClassInstance.single04;
}
}
静态内部类不会立即初始化保证了懒加载。single4是static final 类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,类加载过程是天生线程安全的,从而保证了线程安全性。兼备了高效并发和懒加载的优势
5、枚举方式实现单例模式
package feng.com.GOF;
/**
* 枚举方式实现单例
* @author feng
*
*/
public enum SingletonDemo05 {
// 这个枚举元素 本身就是单例对象
INSTANCE;
// 可以添加自己需要的操作
public void singletonOperation() {
//System.out.println("输出测试");
}
}
枚举实现简单,可能理解起来比较困难。。。枚举本身就是单例模式,有JVM从根本上提供保障!避免通过反射和反序列化漏洞。 不过不能延时加载。
简单测试 运行效率
package com.feng.singleton;
import java.util.concurrent.CountDownLatch;
/**
* 测试多线程环境下五种单例模式的效率
* @author feng
*
*/
public class Client3 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
// CountDownLatch 线程辅助类 可以在线程运行期间等待其他线程执行完毕
int threadNum = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for(int i= 0; i<10; i++) {
new Thread(new Runnable() {
public void run() {
for(int j=0; j<100000; j++) {
Object o = SingletonDemo01.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
// main 线程阻塞,直到计数器变为0 才会继续向下执行
countDownLatch.await(); // 等待阻塞 本质就是循环检测
long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end-start));
}
}
关于选择:
如果没有懒加载要求,那么枚举优于饿汉式。
如果有懒加载需求, 那么静态内部类优于懒汉式。
双重检测锁我也不知道好不好。。。
以下内容了解知道就行 不必深究 我也不懂 大神知道带我飞
单例模式的问题与测试
1、反射破解单例(不可破解枚举)以懒汉式加载为例。一般用不到。
单例类
package com.feng.singleton;
public class SingletonDemo06 {
private static SingletonDemo06 single6;
private SingletonDemo06() { };
public static synchronized SingletonDemo06 getInstance() {
if(single6 == null) {
single6 = new SingletonDemo06();
}
return single6;
}
}
反射破解单例
package com.feng.singleton;
import java.lang.reflect.Constructor;
/**
* 测试反射和反序列化破解单例模式
* @author feng
*
*/
public class Client2 {
public static void main(String[] args) throws Exception {
SingletonDemo06 s6_1 = SingletonDemo06.getInstance();
SingletonDemo06 s6_2 = SingletonDemo06.getInstance();
System.out.println(s6_1);
System.out.println(s6_2);
// 获得Class 对象
Class<SingletonDemo06> clazz = (Class<SingletonDemo06>) Class.forName("com.feng.singleton.SingletonDemo06");
// 获得构造器
Constructor<SingletonDemo06> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true); // 跳过权限检查,也就可以访问私有变量了
SingletonDemo06 s6_3 = constructor.newInstance();
SingletonDemo06 s6_4 = constructor.newInstance();
System.out.println(s6_3);
System.out.println(s6_4);
System.out.println(s6_3 == s6_4);
}
}
constructor.setAccessible(true); // 跳过权限检查,也就可以访问私有变量了。这是关键。这样s6_3与s6_4就不是同一个对象了
防反射破解单例可通过在构造方法种手动抛出异常 谁想出来的。优秀。。。
private SingletonDemo06() {
if(single6 != null) {
throw new RuntimeException();
}
};
这样反射就无法破解了。因为第二次创建对象时会报错。
通过序列化反序列化 生成新的对象。。。
// 序列化
FileOutputStream fos = new FileOutputStream("e:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s6_1);
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e:/a.txt"));
SingletonDemo06 s6_5 = (SingletonDemo06) ois.readObject();
System.out.println(s6_5);
不过序列化反序列化需要SingletonDemo06实现Serializable接口。
public class SingletonDemo06 implements Serializable
这样s6_1与s6_2是同一个对象, 单s6_5是不同的。
防止序列化反序列化操作需在SingletonDemo06中重写一个方法 readResolve()
// 反序列化时直接调用该方法返回指定对象,而不会读取序列化文件产生新对象返回
public Object readResolve() throws ObjectStreamException{
return single6;
}
至于为什么要实现 Serializable 接口,不实现不就好了吗。。。我也不懂。