什么是单例!
什么是单例模式?
保证整个系统中⼀个类只有⼀个对象的实例,实现这种功能的⽅式就叫单例模式。
为什么要⽤单例模式?
1、单例模式节省公共资源
⽐如:⼤家都要喝⽔,但是没必要每⼈家⾥都打⼀⼝井是吧,通常的做法是整个村⾥打⼀个井就够了,⼤家都从这个井⾥⾯打⽔喝。
对应到我们计算机⾥⾯,像⽇志管理、打印机、数据库连接池、应⽤配置。
2、单例模式⽅便控制
就像⽇志管理,如果多个⼈同时来写⽇志,你⼀笔我⼀笔那整个⽇志⽂件都乱七⼋糟,如果想要控制⽇志的正确性,那么必须要对关键的代码进⾏上锁,只能⼀个⼀个按照顺序来写,⽽单例模式只有⼀个⼈来向⽇志⾥写⼊信息⽅便控制,避免了这种多⼈⼲扰的问题出现。
实现单例模式的思路
1. 构造私有:
如果要保证⼀个类不能多次被实例化,那么我肯定要阻⽌对象被new 出来,所以需要把类的所有构造⽅法私有化。
2.以静态⽅法返回实例。
单例模式的几种实现方式因为外界就不能通过new来获得对象,所以我们要通过提供类的⽅法来让外界获取对象实例。
3.确保对象实例只有⼀个。
只对类进⾏⼀次实例化,以后都直接获取第⼀次实例化的对象。
/**
* 单例模式案例
*/
public class Singleton {
//确保对象实例只有⼀个。
private static final Singleton singleton = new Singleton();
//构造⽅法私有
private Singleton() {
}
//以静态⽅法返回实例
public static Singleton getInstance() {
return singleton;
}
}
这⾥类的实例在类初始化的时候已经⽣成,不再进⾏第⼆次实例化了,⽽外界只能通过
tInstance()⽅法来获取SingleCase 对象, 所以这样就保证整个系统只能获取⼀个类的对象实例。
⼏种单例模式的区别
饿汉模式
饿汉模式的意思是,我先把对象(⾯包)创建好,等我要⽤(吃)的直接直接来拿就⾏了。
public class Singleton {
//先把对象创建好
private static final Singleton singleton = new Singleton();
private Singleton() {
}
//其他⼈来拿的时候直接返回已创建好的对象
public static Singleton getInstance() {
return singleton;
}
}
我们上⾯的案例就是使⽤的饿汉模式。 这种模式是最简单最省⼼的,不⾜的地⽅是容易造成资源上的浪费(⽐如:我事先把⾯包都做好了,但是你并不⼀定吃,这样容易造成资源的浪费)。
懒汉模式
因为饿汉模式可能会造成资源浪费的问题,所以就有了懒汉模式,
懒汉模式的意思是,我先不创建类的对象实例,等你需要的时候我再创建。
/**
* 单例模式案例
*/
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
//获取对象的时候再进⾏实例化
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
}
懒汉模式在并发情况下可能引起的问题
懒汉模式解决了饿汉模式可能引起的资源浪费问题,因为这种模式只有在⽤户要使⽤的时候才会实例化对象。但是这种模式在并发情况下会出现创建多个对象的情况。
因为可能出现外界多⼈同时访问Instance()⽅法,这⾥可能会出现因为并发问题导致类被实例化多次,所以懒汉模式需要加上锁synchronized (Singleton.class) 来控制类只允许被实例化⼀次。
如果不加锁并发的情况下会出现这种情况
加锁后就不会出现多个线程同时执⾏相同代码的情况,因为线程是按队列的形式执⾏的,只有当前⼀个线程执⾏完之后才能进⼊代码块。
懒汉模式加锁引起的性能问题
在上⾯的案例中,我们通过锁的⽅式保证了单例模式的安全性,因为获取对象的⽅法加锁,多⼈同时访问只能排队等上⼀个⼈执⾏完才能继续执⾏,但加锁的⽅式会严重影响性能。
解决⽅案⼀:双重检查加锁(DCL)
public static Singleton getInstance() {
if (singleton == null) {//先验证对象是否创建
synchronized (Singleton.class) {//只有当对象未创建的时候才上锁
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
双检测锁定的⽅式 是只有当对象未创建的时候才对请求加锁,对象创建以后都不会上锁,这样有效的提升了程序的效率,也可以保证只会创建⼀个对象的实例。
DCL是完美的解决了单例模式中性能和资源浪费的问题,但是DCL在并发情下也会存在⼀个问题,因为Jvm指令是乱序的;
情况如下:
线程1调⽤getInstance 获取对象实例,因为对象还是空未进⾏初始化,此时线程1会执⾏new Singleton()进⾏对象实例化,⽽当线程1的进⾏new Singleton()的时候JVM会⽣成三个指令。
指令1:分配对象内存。
指令2:调⽤构造器,初始化对象属性。
指令3:构建对象引⽤指向内存。
因为编译器会⾃作聪明的对指令进⾏优化, 指令优化后顺序会变成这样:
1、执⾏指令1:分配对象内存,
2、执⾏指令3:构建对象引⽤指向内存。
3、然后正好这个时候CPU 切到了线程2⼯作,⽽线程2此时也调⽤getInstance获取对象,那么线程2将执⾏下⾯这个代码 if (singleton == null),此时线程2发现对象不为空(因为线程1已经创建对象引⽤并分配对象内存了),那么线程2会得到⼀个没有初始化属性的对象(因为线程1还没有执⾏指令2)。
所以在这种情况下,双检测锁定的⽅式会出现DCL失效的问题。
解决⽅案⼆:⽤内部类实现懒汉模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHoler.singleton;
}
//定义静态内部类
private static class SingletonHoler {
//当内部类第⼀次访问时,创建对象实例
private static Singleton singleton = new Singleton();
}
}
静态内部类原理:
当外部内被访问时,并不会加载内部类,所以只要不访问SingletonHoler 这个内部类, private static Singleton singleton = new Singleton() 不会实例化,这就相当于实现懒加载的效果,只有当SingletonHoler.singleton 被调⽤时访问内部类的属性,此时才会将对象进⾏实例化,这样既解决了恶汉模式下可能造成资源浪费的问题,也避免了了懒汉模式下的并发问题。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论