java 单例模式这个要怎么理解?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java 单例模式这个要怎么理解?相关的知识,希望对你有一定的参考价值。
适用性:当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。这句话怎么理解
参考技术A 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
介绍
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应用实例:
1、一个班级只有一个班主任。
2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
用JS来理解设计模式—— 单例模式
单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点。
单例模式应该算是一个设计模式中比较简单且好用的模式,大部分人或多或少都接触过这个模式。所以也不举什么例子了,先来问几个问题,如果都了解的话,也就不用往下看了。
- 单例模式有什么用,什么时候用?
- 单例模式怎么写?
- 在JS里用单例模式时可能会出什么问题?
单例模式有什么用,什么时候用?
很简单,在上面的定义以及模式的名字就能看出来,这个模式的用处就是保证我们拿到的对象永远是同一个,不论是在代码的什么地方,也不论是在何时。
举几个例子,比方说一个提示弹窗,一般来说这种提示弹窗通常不会同时展示多个出来,但假如短时间内多次调用生成提示弹窗的方法,则有可能会出现多个提示弹窗。但假若采用了单例模式来,我们拿到的永远是同一个提示弹窗,只会在同一个提示弹窗上进行修改,就不用担心多个弹窗出现的情况。
再比方说,当我们创建的一个对象所花销的时间较长,且这个对象属于可复用的,又可能需要频繁使用时,就可以采用单例模式,比较常见的就是创建编辑器这类对象等等。
书里有提到一个大家可能会有的问题,那就是写一个大家约定好的全局变量不也一样吗?因为这样也能够实现单例模式所需要的效果,无论是在何时何地拿到的都是同一个对象。确实这样子也是可以做到单例的效果,但是有几个问题。
1.假如创建这个对象过程非常消耗资源,然后我们却一直没用使用到,那么不就是形成浪费了吗,即浪费了生成的时间,也浪费了其占据的内存空间。
2.如何保证这个对象只会被实例化一次?生成这个对象的方法会不会暴露出来,使得其他开发人员选择在其他地方自己调用?
这里也说到了一个很重要的点,就是构造这个对象的方法应当是“私有”的,不能够被直接调用,否则这个方法也没什么意义了。
单例模式怎么写?
首先需要和大家说明,我写这个代码是根据自己的理解来写的,写之前没参考别人的写法,所以可能会有一些问题,欢迎大家指出。
let getTestInstance = (function () {
let instance = null;
class Test {
constructor (name) {
this.name = name || \'test\'
}
changeName (name) {
this.name = name;
}
}
return function (...args) {
if (!instance) instance = new Test(...args);
return instance;
}
})();
let a = getTestInstance();
console.log(a.name); // test
a.changeName(\'name change\');
console.log(a.name); // name change
let b = getTestInstance();
console.log(b.name); // name change
b.changeName(\'name change2\');
console.log(b.name); // name change2
console.log(a.name); // name change2
console.log(a === b); // true
这里的实现比较简单,主要是通过闭包,将我们的单例和构造函数“私有化”,无法直接访问,从而保证拿到的都是同一个对象,且只能通过getTestInstance
方法来获取。这样我们就能确保一个类只有一个实例,且只能通过我提供的这个访问点来获取,这样就符合了单例模式的要求。
单例模式确保一个类只有一个实例,并提供一个全局访问点。
我们可以看到变量a与变量b其实指向的都是同一个实例,因此不论是a对实例的改变或是b,都会互相影响。
这里的代码实现例子是一个简单的例子,不过还有一些特殊的情况需要考虑。
在JS里用单例模式时可能会出什么问题?
- 多线程问题
- 异步生成实例问题
多线程问题
多线程问题指的是假若有几处代码同时调用了获取实例方法,且实例尚且未生成,那么可能出现这几处的实例不一致的情况。众所周知JS是一个单线程模型,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。
不过现在有了webWorker,JS也拥有了多线程环境,那么我们就可以进行尝试在JS多线程中是否会存在单例模式的多线程问题。
思路是:新建多个webworker,并且在webWorker中引入getTestInstance
方法,然后将webWorker中的生成的实例返回给主流程,在主流程中进行对象比较。
然而经过尝试发现,在webWorker中使用importScripts
引入的js是互相独立的,这意味着生成的实例都是不一样的,那么引入js这条路子就断了。
因此我就想办法将主流程中的getTestInstance
方法传入到webWorker里面,其中我采用了两种方法,但都以失败告终。分别是:
- 将
getTestInstance
方法用postMessage
传入。 - 由于webWorker中线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用
document
、window
、parent
这些对象。但是,Worker 线程拥有navigator
对象和location
对象。利用这个特点,我尝试将getTestInstance
方法挂在location
对象上。
但两者的结果分别是:
- 无法将方法传入到webWoker,这里应该是底层限制,无法解决。
app.js:15 Uncaught DOMException: Failed to execute \'postMessage\' on \'Worker\': function (...args) {
if (!instance) instance = new Test(...args);
return instance;
} could not be cloned.
- 虽然webWorker确实拥有
location
对象,但它的location
对象很特殊,是WorkerLocation
,且不同Worker之间的WorkerLocation
都是独立的。因此无法在location
对象上获取到getTestInstance
方法。
因此可以片面的说在JS多线程中无法使用单例模式。
异步生成实例问题
这个问题主要是讨论如果生成实例的方法是一个异步的方法,那么在异步方法执行完成前,有别处代码调用了获取实例方法,那么是不是会产生多次调用或实例不一致的问题?
因此实现上有所不同,但其实也很简单,就是返回的值变成Promise且多加几个状态控制而已,下面是实现代码,如有遗漏或错误,欢迎指出。
let getTestInstance = (function () {
let instance = null;
let loading = false; // 判断是否正在生成中
let loadingPrmoise = null; // 用于记录第一次生成的promise
class Test {
constructor (name) {
this.name = name || \'test\'
}
changeName (name) {
this.name = name;
}
}
return function (...args) {
if (instance) return Promise.resolve(instance);
if (loading) return loadingPrmoise;
loading = true;
// 一个2S的生成实例过程
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
instance = new Test(...args);
loading = false;
resolve(instance);
}, 2000)
});
loadingPrmoise = promise;
return promise;
}
})();
let a,b;
// 生成实例需要2S
getTestInstance().then(res => {
a = res;
});
// 1S秒再次获取实例
setTimeout(() => {
getTestInstance().then(res => {
b = res;
console.log(a === b) // true
getTestInbstance().then(res => {
console.log(a === res); // true
})
});
}, 1000);
以上是关于java 单例模式这个要怎么理解?的主要内容,如果未能解决你的问题,请参考以下文章