单例模式原理
什么是单例对象?
有些对象我们只需要一个如线程池、缓存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为静态变量分配一次内存空间。
这个场景我们想象一下:一个食品工厂,工厂只有一个,然后工厂里也只有一个锅,制作完一批食品才能制作下一批,这个时候我们的食品工厂对象就是单例的了,下面就是模拟实现的代码,代码的单例实现和上面的简单实现不同,做了优化处理,稍后会解释为什么要优化
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) {}
判断里面的,只有符合条件才会进入同步方法,减少了性能消耗。
最后
大家觉得不错可以点个赞在关注下,以后还会分享更多文章!同时我的的专栏:Java架构技术栈,以后还会分享更多文章!
网友评论