什么是单例模式
定义:
单例模式,是一种常用的软件设计模式。在他的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例,即一个类只有一个对象实例。
作用:
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
特点:
(1)单例类只能有一个实例
(2)单例类必须自己创建自己的唯一实例
(3)单例类必须给其他的对象提供这一实例
单例模式的要点:
(1)私有的构造方法
(2)指向自己实例的静态引用
(3)以自己实例为返回值的静态的公有方法
单例模式有五种写法:
(1)饿汉式
(2)懒汉式
(3)双重锁的形式
(4)静态内部类式
(5)枚举式
饿汉式单例:
线程安全,调用效率高,但是不能延迟加载
代码如下:
public class Demo01 {
private static Demo01singleton =new Demo01();
private Demo01() {
}
public static Demo01 getInstance() {
return singleton;
}
}
优点:饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题,因此,可以省略synchronized关键字
缺点:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源的浪费。
懒汉式单例:
线程安全,调用效率不高,但是,可以延迟加载
代码如下:
public class Demo02 {
private static Demo02singleton;
private Demo02() {
}
public static synchronized Demo02 getInstance() {
if(singleton==null) {
singleton=new Demo02();
}
return singleton;
}
}
优点:可以实现延迟加载(懒加载),真正使用的时候才加载
缺点:资源利用率高了,但是,每次调用getInstance()方法都要同步,并发效率较低。
双重锁形式的单例:
由于jvm底层内部模型原因,偶尔会出问题,不建议使用
代码如下:
public class Demo03 {
private static Demo03instance=null;
public static Demo03 getInstance() {
if (instance ==null) {
Demo03sc;
synchronized (Demo03.class) {
sc =instance;
if (sc ==null) {
synchronized (Demo03.class) {
if(sc ==null) {
sc =new Demo03();
}
}
instance =sc;
}
}
}
return instance;
}
private Demo03() {
}
}
优点:这个模式将同步内容下方到if内部,提高了执行效率不必每次获取对象时都要进行同步,只有第一次才同步创建了以后就没必要了。
缺点:由于编译器优化原因和jvm底层内部模型的原因,偶尔会出现问题,不建议使用。
静态内部类式:
线程安全,调用效率高,可以延迟加载
代码如下:
public class Demo04 {
private static class SingletonClassInstance{
private static final Demo04instance=new Demo04();
}
private Demo04() {
}
public static Demo04 getInstance() {
return SingletonClassInstance.instance;
}
}
优点:
(1)外部类没有static属性,则不会像饿汉式那样立刻加载对象
(2)只有真正调用了getInstance(),才会加载静态内部类。加载类时是线程安全的。Instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
(3)兼备了并发高效调用和延迟加载的优势
枚举式单例:
线程安全,调用效率高,不能延迟加载
代码如下:
public enum Demo05 {
//这个枚举元素,本身就是单例对象!
INSTANCE;
//添加自己需要的操作!
public void singletonOperation(){
}
}
优点:实现简单,枚举本身就是单利模式,由于jvm从根本上提供了保障!避免通过反射和反序列化的漏洞!
缺点:无延迟加载
总结单例模式的优缺点:
优点:
(1)在内存中只有一个对象,节省内存空间。
(2)避免频繁的创建销毁对象,可以提高性能。
(3)避免对共享资源的多重占用。
(4)可以全局访问
缺点:
(1)扩展困难,由于getInstance静态函数没有办法生成子类的实例。如果要拓展,只有重写那个类。
(2)隐式使用引起类结构不清晰。
(3)导致程序内存泄露的问题。
单例模式的适用场景
由于单例模式的以上优点,所以是编程中用的比较多的一种设计模式。以下为使用单例模式的场景:
(1)需要频繁实例化然后销毁的对象。
(2)创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
(3)资源共享的情况下,避免由于资源操作时导致的性能或损耗等
(4)控制资源的情况下,方便资源之间的互相通信。
单例模式注意事项:
(1) 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
问题:1/反射可以破解上面几种(不包含枚举式)实现方式!
决解办法:可以在构造方法中手动抛出异常
代码如下:
//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。
private static Demo6instance;
private Demo6(){//私有化构造器
if(instance!=null){
throw new RuntimeException();
}
}
//方法同步,调用效率低!
public static synchronized Demo6 getInstance(){
if(instance==null){
instance =new Demo6();
}
return instance;
}
2/反序列化可以破解上面几种(不包含枚举式)实现方法
解决办法:可以通过定义readResolve()防止获取不同的对象(实际是一种回调)
代码如下:
//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。
private static Demo6instance;
private Demo6(){//私有化构造器
}
//方法同步,调用效率低!
public static synchronized Demo6 getInstance(){
if(instance==null){
instance =new Demo6();
}
return instance;
}
//反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!
private Object readResolve()throws ObjectStreamException {
return instance;
}
(2)不要做断开单例类对象与类中静态引用的危险操作。
(3)多线程使用单例使用共享资源时,注意线程安全问题。