单例模式:
- 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的特点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例
为什么使用单例模式:
- 当我们需要确保某个类只要一个对象,或创建一个类需要消耗的资源过多,如访问IO和数据库操作等,这时就需要考虑使用单例模式了。
比如说当我们使用多线程在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
数据库连接实例主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为用单例模式来维护,就可以大大降低这种损耗。
单例模式
1、懒汉式,线程不安全:
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
public class DanLiMoShi1 {
private static DanLiMoShi1 instance;
private DanLiMoShi1 (){}
public static DanLiMoShi1 getInstance() {
if (instance == null) { //如果有了实例了,就不需要要创建了
instance = new DanLiMoShi1();
}
return instance;
}
public void SayLove(){
System.out.println("Love You Baby!");
}
}
//测试一下
public class Test1 {
public static void main(String[] args){
DanLiMoShi1 danli1 = DanLiMoShi1.getInstance();
DanLiMoShi1 danli2 = DanLiMoShi1.getInstance();
if( danli1 == danli2 )
System.out.println("我们是一样的");
danli1.SayLove();
}
}
//运行结果:
//我们是一样的
//Love You Baby!
2、懒汉式,线程安全:
必须加锁 synchronized 才能保证单例,但加锁会影响效率。
public class DanLiMoShi2 {
private static DanLiMoShi2 instance;
private DanLiMoShi2 (){}
public static synchronized DanLiMoShi2 getInstance() {
if (instance == null) { //如果有了实例了,就不需要要创建了
instance = new DanLiMoShi2();
}
return instance;
}
public void SayLove(){
System.out.println("Love You Baby!");
}
}
3、饿汉式,线程安全:
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
public class DanLiMoShi3 {
private DanLiMoShi3() {
}
private static final DanLiMoShi3 single = new DanLiMoShi3();
public static DanLiMoShi3 getInstance() { //静态工厂方法
return single;
}
public void SayLove(){
System.out.println("Love You Baby!");
}
}
测试:
public class Test3 {
public static void main(String[] args) {
DanLiMoShi3 danli1 = DanLiMoShi3.getInstance();
DanLiMoShi3 danli2 = DanLiMoShi3.getInstance();
if( danli1 == danli2 )
System.out.println("我们是一样的");
danli1.SayLove();
}
}
//运行结果
//我们是一样的
//Love You Baby!
4、双检锁/双重校验锁(DCL,即 double-checked locking)
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class DanLiMoShi4 {
private volatile static DanLiMoShi4 singleton;
private DanLiMoShi4 (){}
public static DanLiMoShi4 getInstance() {
if (singleton == null) {
synchronized (DanLiMoShi4.class) {
if (singleton == null) {
singleton = new DanLiMoShi4();
}
}
}
return singleton;
}
}
//测试
public class Test4 {
public static void main(String[] args) {
DanLiMoShi4 danli1 = DanLiMoShi4.getInstance();
DanLiMoShi4 danli2 = DanLiMoShi4.getInstance();
if( danli1 == danli2 )
System.out.println("我们是一样的");
}
}
//运行结果:
//我们是一样的
为什么这种能提升性能呢?
如果直接加锁,那么10个线程来了,那这10个线程需要排队;但是采用双检锁/双重校验锁的方式,10个线程来了,如果已经有实例了,直接返回实例,这样就不需要排队了,从而提高了性能。