设计模式是在大量的实践中总结和理论化之后优的代码结构、编程风格、以及解决问题的思考方式。
单例模式
什么是单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
单列有如下几个特点:
- 在Java应用中,单例模式能保证在一个JVM中,该对象只有一个实例存在
- 构造器必须是私有的,外部类无法通过构造器方法创建该实例
- 没有set方法,外部类无法调用set方法来创建该实例
- 提供一个公开的get方法获取唯一的这个实例
单例模式使用地方
单列模式一般用在对实例输量有严格要求的地方,比如数据池,线程池,缓存,session会话等等。
饿汉式
class Singleton {
//1.私化类的构造器
private Singleton (){
}
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的
private static Singleton instance = new Singleton ();
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
懒汉式
这里的写法是线程不安全的
class Singleton {
//1.私化类的构造器
private Singleton (){
}
//2.声明当前类对象,没初始化
//4.此对象也必须声明为static的
private static Singleton instance = null;
//3.声明public、static的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){
instance = new Singleton ();
}
return instance;
}
}
线程安全的懒汉式
- 加synchronized关键字
此方法是最简单又有效的方法,不过对性能上会有所损失。比如两个线程同时调用这个实例,其中一个线程要等另一个线程调用完才可以继续调用。而线程不安全往往发生在这个实例在第一次调用的时候发生,当实例被调用一次后,线程是安全的,所以加synchronized就显得有些浪费性能。
public class Singleton {
private static Singleton instance ;
private Singleton(){
}
public synchronized static Singleton getInstance(){
if(instance ==null){
instance =new Singleton();
}
return instance ;
}
}
- 用双检锁做两次判断,代码如下DCL:
public class Singleton {
private static Singleton instance ;
private Singleton(){
}
public static Singleton getInstance(){
if(instance ==null){
synchronized(Singleton.class){
if(instance==null)
instance =new Singleton();
}
}
return instance ;
}
}
将synchronized关键字加在内部,也就是说当调用的时候是不需要加锁的,只有当instance为null,并创建对象的时候才需要加锁,性能上有了一定的提升。
但是这样就没有问题了吗?
上面的类的getInstance函数的字节码文件为:
0 getstatic #2 <泛型/Singleton.instance>
3 ifnonnull 37 (+34)
6 ldc #3 <泛型/Singleton>
8 dup
9 astore_0
10 monitorenter
11 getstatic #2 <泛型/Singleton.instance>
14 ifnonnull 27 (+13)
17 new #3 <泛型/Singleton>
20 dup
21 invokespecial #4 <泛型/Singleton.<init>>
24 putstatic #2 <泛型/Singleton.instance>
27 aload_0
28 monitorexit
29 goto 37 (+8)
32 astore_1
33 aload_0
34 monitorexit
35 aload_1
36 athrow
37 getstatic #2 <泛型/Singleton.instance>
40 areturn
其中的instance =new Singleton(); 被分解为了
//首先在堆上面申请一块内存,这个时候对象里面的属性复制为初始值
17 new #3 <泛型/Singleton>
20 dup
//调用构造函数,给成员变了赋值
21 invokespecial #4 <泛型/Singleton.<init>>
//把引用赋值给instance
24 putstatic #2 <泛型/Singleton.instance>
上图中可以看出线程2 拿到了没有初始化的对象。如果没有看懂,看下图
public class Singleton {
private static volatile Singleton instance ;
private Singleton(){
}
public static Singleton getInstance(){
if(instance ==null){
synchronized(Singleton.class){
if(instance==null)
instance =new Singleton();
}
}
return instance ;
}
}
通过加上volatile修饰变量,能够防止指令重排序
volatile的作用
扫描二维码关注公众号,回复:
12888387 查看本文章

- 防止指令重排序
- 保证内存可见
但是这个volatile关键字可能会使得jvm减少对代码的优化,所以运行效率不高
使用静态内部类
静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载就不去初始化对象,故而不会占用内存。只有当调用getInstance()方法第一次被调用时,才会去初始化instance,第一次调用getInstance()方法会导致虚拟机加载内部类,这种方法不仅能确保线程安全,也能确保单列的唯一性.
public class Singleton {
private Singleton(){
}
public static class SingletonPro{
private static Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingletonPro.instance;
}
}
枚举类
使用枚举类不仅可以解决线程同步,还可以防止反序列化。
public enum Singleton{
INSTANCE;
}
使用枚举来实现单实例控制会更加简洁,而且JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。
总结
个人觉得最好写的就是静态内部类了,同时效率也高。