Java设计模式-单例模式

单例模式原理

什么是单例对象?

有些对象我们只需要一个如线程池、缓存dataSource、硬件设备等。如果有多个实例会造成相互冲突、结果不一致的问题,毕竟你有我也有,但是你有的和我有的不一定真的一模一样,是同一个。使用单例模式可以确保一个类最多只有一个实例,并提供一个全局的访问点。

public class Test {
    public class ABC {
        public ABC() {
        }

//        private ABC() {  //为这个内部类申明私有的构造方法,外部则无法通过new初始化,只能类自己初始化
//        }
//        ABC n1 = new ABC();
    }

    public class CDB {
        public CDB() {
            ABC n1, n2;
            n1 = new ABC();
            n2 = new ABC();
            System.out.println("CBD: " + (n1 == n2));    //false
        }
    }

    public static void main(String[] args) {
        ABC n1, n2;
        n1 = new Test().new ABC();
        n2 = new Test().new ABC();
        System.out.println("main: " + (n1 == n2));   //false
        new Test().new CDB();
    }
}
复制代码

那么有什么方法可以使得每次new出来的对象都是同一个呢,看看下面单例模式类图,就可以找到一些思路了!

Singleton(单例)

static uniqueInstance(静态的唯一对象申明)

private singleton() (私有的实例化方法)

static getInstance() (全局访问点)

编码实战

了解了上面的内容,我们来写一个简单的单例模式代码,代码如下:

public class Singleton {
    private static Singleton uniqeInstance = null;    //静态变量

    private Singleton() {    // 私有的构造方法,外部无法使用
    }

    public static Singleton getInstance() {
        if (uniqeInstance == null) {
            uniqeInstance = new Singleton();
        }
        return uniqeInstance;
    }
}
复制代码

静态变量由于不属于任何实例对象,是属于类的,所以在内存中只会有一份,在类的加载过程中,JVM为静态变量分配一次内存空间。 ===> Java之static静态关键字详解

这个场景我们想象一下:一个食品工厂,工厂只有一个,然后工厂里也只有一个锅,制作完一批食品才能制作下一批,这个时候我们的食品工厂对象就是单例的了,下面就是模拟实现的代码,代码的单例实现和上面的简单实现不同,做了优化处理,稍后会解释为什么要优化

public class ChocolateFactory {
    private boolean empty;   // 空锅
    private boolean boiled;  // 加热
    public volatile static ChocolateFactory uniqueInstance = null;

    private ChocolateFactory() {
        empty = true;    // 锅是空的
        boiled = false;  // 还没加热
    }

    public static ChocolateFactory getInstance() {
        if (uniqueInstance == null) {
            synchronized (ChocolateFactory.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new ChocolateFactory();
                }
            }
        }
        return uniqueInstance;
    }
    // 第一步装填
    public void fill() {
        if (empty) {  // 锅是空的
            // 添加原料巧克力动作
            empty = false;  // 锅装满了,不是空的
            boiled = false;  // 还没加热
        }
    }
    // 第三步倒出
    public void drain() {
        if ((!empty) && boiled) {  // 锅不是空的,已经加热
            // 排出巧克力动作
            empty = true;   //出锅,锅空了
        }
    }
    // 第二步加热
    public void boil() {
        if ((!empty) && (!boiled)) {  // 锅不是空的,没加热
            // 煮沸
            boiled = true;  // 已经加热
        }
    }
}

复制代码

单例模式的问题及优化

问题

在多线程的情况下,会有时间片的概念,cpu竞争,这刚好就是单例模式可能会发生问题的时候,会发生什么样的问题呢?以食品加工厂代码为例

public synchronized static ChocolateFactory getInstance() {
       if (uniqueInstance == null) {
           uniqueInstance = new ChocolateFactory();
       }
       return uniqueInstance;
   }
复制代码

在多线程情况下会实例化出两个对象

优化解决

同步(synchronized)getInstance方法

线程1执行到if (uniqueInstance == null),被线程2抢走了执行权,此时线程1还没有new对象;线程2同样来到if (uniqueInstance == null),发现没有对象实例,也打算实例化对象;最后线程1线程2都会执行uniqueInstance = new ChocolateFactory();此时可以在getInstance()方法前加上synchronized修饰符同步方法,但是在多线程调用比较频繁的时候,这种方式比较耗费性能。

“急切”创建实例

public class ChocolateFactory {
    public static ChocolateFactory uniqueInstance = new ChocolateFactory();  //“急切”创建实例
    public static ChocolateFactory getInstance() {
       if (uniqueInstance == null) {
           uniqueInstance = new ChocolateFactory();
       }
       return uniqueInstance;
   }
}
复制代码

public static ChocolateFactory uniqueInstance = new ChocolateFactory();在应用启动的时候就加载初始化一次实例对象,这个时候多线程调用永远也只会有一个实例,因为if (uniqueInstance == null)的结果一直是false;但如果这对单例对象在应用中没有地方用到,使用这种方式则耗费掉了一些内存空间

双重检查加锁(最佳)

public class ChocolateFactory {
    //用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。
    public volatile static ChocolateFactory uniqueInstance = null;   
    public static ChocolateFactory getInstance() {
        if (uniqueInstance == null) {
            synchronized (ChocolateFactory.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new ChocolateFactory();
                }
            }
        }
        return uniqueInstance;
    }
}
复制代码

首先public volatile static ChocolateFactory uniqueInstance = null;没有在应用启动的时候就初始化对象,节省了内存;其次synchronized修饰的代码块是再if (uniqueInstance == null) {}判断里面的,只有符合条件才会进入同步方法,减少了性能消耗。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券