简介
Java中设计模式主要分为三种,创建型模式,结构型模式,行为型模式
单例模式则属于创建型模式,它提供了一种创建对象的方式
顾名思义就是一个类只有单个对象被创建,并且外部类可以直接去访问该对象,而不需要再去实例化,很像类中的静态方法直接调用即可
总结:
单例类只能由自己创建自己的唯一实例,并且给其他对象提供这一实例
优点:减少频繁创建对象浪费资源
缺点:无法像直接去创建对象那样灵活
示例:
public class Single {
// 创建对象
private static Single single;
//让构造函数为 private,这样该类就不会被实例化,也就是无法被new(如果不声明private,Java会自定义一个无参的public构造函数)
private Single(){}
// 获取唯一可用的对象
public static Single getInstance() {
return single;
}
}
让我们在另一个类中测试一下
public class Demo {
public static void main(String[] args) {
Single instance = Single.getInstance();
instance.sayHello();
}
}
这样我们不论在多少个类中去使用Single中的sayHello方法,其实都是用的同一个对象,这样可以减少频繁的创建和销毁对象所浪费的资源
接下来让我们看一下单例模式的几种实现方式
1.懒汉式,线程不安全
public class Single {
private static Single single;
private Single(){}
public static Single getInstance() {
if (single == null) {
single = new Single();
}
return single;
}
}
根据代码,我们可以看到它在创建对象实例的时候去做了判断,如果没有对象则去创建,如果已有则直接使用。
但问题显而易见,在多线程环境下,假如两个线程同时进入到这个方法,那么可能就会创建出多个实例对象,所以该方法在多线程环境下可能会有问题
2.懒汉式,线程安全
public class Single {
private static Single single;
private Single(){}
public static synchronized Single getInstance() {
if (single == null) {
single = new Single();
}
return single;
}
}
这种方式与第一种方式唯一的不同就是加了锁,在获取对象的时候锁住,这样就使得两个线程没办法同时进入这个方法,保证了线程安全问题,但缺点也很明显,每次使用该对象的时候都去加锁判断,会对效率产生一定的影响
3.饿汉式,线程安全
public class Single {
private static Single single = new Single();
private Single(){}
public static Single getInstance() {
return single;
}
}
在类加载时就去实例化,相比第一种避免了线程安全问题,相比第二种提升了效率,但可能会浪费一定的资源
4.双检锁
public class Single {
private volatile static Single single;
private Single() {
}
public static Single getInstance() {
if (single == null) {
synchronized (Single.class) {
if (single == null) {
single = new Single();
}
}
}
return single;
}
}
这种方式主要是为了解决第二种方式中的效率问题,第二种方式中我们将整个getInstance锁住了,但其实我们的主要目的就是避免重复创建对象
所以我们可以将判断对象是否存在拿到锁的外面,也就是第一个if (single == null),这一步主要是解决假设该对象实例已经存在,那么就没必要每次都去加锁判断了,而是直接判断就行
如果这里为true,证明对象不存在,需要去新建,这里有了个synchronized 锁,这里的锁的作用其实是和方式二中的锁一样的,去避免多线程情况下重复创建对象
5.静态内部类
public class Single {
private static class SingletHolder {
private static final Single SINGLE = new Single();
}
private Single() {}
public static Single getInstance() {
return SingletHolder.SINGLE;
}
}
这种方式其实像是第三种方式的优化,方式三中将实例化对象设为静态,在类加载的时候调用,会造成一定程度的资源浪费
但这种方式在加载类的时候不会去调用,因为静态内部类的优点在于,外部类加载时不会立即加载内部类,所以不会去实例化对象,所以当Single类被加载时,不会去加载SingletHolder类,当getInstance()方法被调用时才会去加载,这样就保证了资源的高效利用,至于内部类如何保证了线程安全问题,这里其实是由虚拟机保证的,虚拟机会保证如果有多个线程去初始化同一个类并执行某个方法时,只会有一个线程去执行,其他线程都会阻塞
总结
饿汉式:优点在于适用于多线程,调用速度快,因为一开始就创建好了对象,缺点是浪费资源
懒汉式:多线程情况下需要通过双检锁等手段处理,性能会降低,调用时初始化时间长
其实是正好相反的两种模式,看需要具体使用哪个