无论是哪种设计模式,自己啃哪本书哪个博客去学,都会考虑最起码的两个问题:
- 我到底该咋用这种设计模式呀,直接把书上的、百度上的、博客上…的程序们抄过来?
- 那我该咋用呢?就算把人家程序抄过来,抄过来放在哪里呀?
- 学了设计模式尤其是单例模式之后,看到XXX类的一个实例XXX,要能反映出来,哦,单例模式可以实现这个需求呀。
开唠~
咱得先知道官方定义的单例模式(Ensure a class has only one instance, and provide a global point of access to it.)都有啥形式。
咱现在假设哈,咱们一个类中只有一个构造器(构造方法,方法可以重载,咱们假设一种只是为了好说一点而已)。
- 你去,把你的类的构造方法(构造器)的权限修饰符改成private
- 再来个getInstance(…)方法,像下面写法一这样写,好,你的类已经算是用上了单例模式了。
//写法一:**
## 这个是强烈推荐使用的解法一(其他解法都有些复杂,加了锁,you know,就会耗时间并且效率就会下降)
**
pubic class SingletonDemo{
//将构造器设置为private,是咱们单例模式的谈资呀,相当于一切的出发点呀。
private SingletonDemo(){
}
private static SingletonDemo singletonDemo = new SingletonDemo();
public static SingletonDemo getInstance(){
return singletonDemo;
}
}
写法一有几个注意点:
- private static SingletonDemo singletonDemo = new SingletonDemo();加了这个static相当于我们使得**
SingletonDemo类的构造器变为静态构造器
**了。(这样以来,java在调用静态构造函数(或者说类加载的时候)就会初始化静态变量singletonDemo )- 也就是说singletonDemo实例在此种写法下不是在getInstance()方法被第一次调用时创建的,
而是在SingletonDemo这个类第一次比别人用到时singletonDemo就会创建
。
- 也就是说singletonDemo实例在此种写法下不是在getInstance()方法被第一次调用时创建的,
- java能够确保静态构造函数只会被调用一次(这个静态构造函数啥时候会被调用,咱们程序员是决定不了的,而是当JVM运行起来时发现第一次使用某个类(型)时自动调用该类(型)的静态构造函数)。 就可以保证singletonDemo 这个静态变量只会被初始化一次(就确保只有一个实例喽)
- Notes:强烈推荐的写法一也是有缺陷的,就是当咱们SingletonDemo类中有一个静态方法时(众所周知咱们调用此静态方法是不需要singletonDemo这个实例的(或者叫对象引用)),但是如果按照咱们这个推荐的解法一来写的话,凡是static的东东都会被一块打包运行(当那个static方法被调用到时这个静态构造器也就会被一块打包执行了,所以singletonDemo也会被无辜的创建出来?,但是我此时用不到singletonDemo,你把他创建出来不无辜?不浪费内存?)
所以,亚古兽进化—强烈推荐使用的写法二:按需创建实例
(其他解法都有些复杂,加了锁,you know,就会耗时间并且效率就会下降)
//写法二
public class SingletonDemoToNeed{
private SingletonDemoToNeed(){
}
public static SingletonDemoToNeed getInstance(){
return Nested.singletonDemoToNeed;//当咱们第一次用到这个嵌套内部类时,会调用这个内部类的静态构造函数创建SingletonDemoToNeed的实例singletonDemoToNeed。
}
//内部定义一个私有内部类Nested,其他人无法使用到这个内部类Nested
class Nested{
static Nested(){
private static SingletonDemoToNeed singletonDemoToNeed = new SingletonDemoToNeed();
}
}
}
- Notes:此时只有当咱们第一次尝试通过SingletonDemoToNeed.getInstance()得到SingletonDemoToNeed的实例singletonDemoToNeed时,会自动调用Nested这个内部类的静态构造函数创建singletonDemoToNeed实例。如果咱们不调用SingletonDemoToNeed.getInstance()时那么也不会调用到内部类从而也就不会创建singletonDemoToNeed实例
===============================分界线
前面两种比较实用,但是后面几种不太实用,但是算作值得一学的解法,外行看热闹内行看门道嘛。
不好的写法三:只在单线程中适用,亚古兽在面对一个打手(熟悉的打手:单线程情况下)时适用,但是多线程条件下就会出现错误(没有用锁)
- 为什么说这是只在单线程中适用的写法,在多线程中就不安全呢。这是因为呀,如果有两个线程同时运行到判断singlrton是否为null的if判断语句中,此时也确实这两个线程谁也没有创建SingletonDemo类的实例时,那么两个线程此时都会创建一个实例,那不就打脸了嘛。(一个线程A执行 到new SingletonDemoOnlyUseSingleThread(),但还没有获得对象(对象初始化是需要时间的),第二个线程 B也在执行,执行到(singletonDemoOnlyUseSingleThread == null)判断,那么线程B获得判断条件也是为真,于是继续 运行下去,线程A获得了一个对象,线程B也获得了一个对象,)这就是说单例模式结果你创建了俩。
//不好的写法三:
pubic class SingletonDemoOnlyUseSingleThread{
//将构造器设置为private,是咱们单例模式的谈资呀,相当于一切的出发点呀。
private SingletonDemoOnlyUseSingleThread(){
}
private static SingletonDemoOnlyUseSingleThread singletonDemoOnlyUseSingleThread = null;
public static SingletonDemoOnlyUseSingleThread getInstance(){
if(singletonDemoOnlyUseSingleThread == null){
return new SingletonDemoOnlyUseSingleThread();
}
}
}
不好的写法四,拆了东墙补西墙(加锁影响效率哦):
public class SingletonDemoToMultiThread{
private SingletonDemoToMultiThread(){
}
//实现一个可重入锁
private final ReentrantLock lock = new ReentrantLock();
private static SingletonDemoToMultiThread singletonDemoToMultiThread = null;
public static SingletonDemoToMultiThread getInstance(){
lock.lock();//加锁
try{
if(singletonDemoToMultiThread == null){
return new SingletonDemoToMultiThread();
}
} catch(Exception e) {
e.printStack();
} finally {
//解锁
lock.unlock();
}
}
}
- 这样一来为什么在多线程条件下就行了呢。此时是这样的,假设有两个线程同时想进入if判断中创建一个实例,但是由于咱们
加了锁
,就确保了一个时刻只有一个线程能得到同步锁
并执行实例化代码创建出实例
,当第一个线程得到锁给自己加上锁之后并去运行if判断体里面的实例化代码,第二个线程只能等待。当第一个线程进入了if判断体后发现实例还没有创建时他就会自己创建一个实例出来,然后代码执行完第一个线程就会释放同步锁。接着第二个线程获得锁给自己加上同步锁并运行相应的代码,但是这个时候由于第一个线程已经把实例创建出来了所以第二个线程就不会再重复创建线程了。这样就保证了我们在多线程环境下也确保能得到一个实例。- 可以像上面那样用Lock对应的锁
- 也可以用Synchronized
- 可以在getXxx()方法前加
- 可以在getXxx()方法内加
Notes:虽然上面加了锁,但是还能再优化一下。我们只是在实例还没创建之前需要加锁操作,但是如果这个实例在其他地方已经被其他人创建出来了,那咱们肯定就不用再加这个锁了呀(加锁很耗时的哟)。那咱们怎么知道当咱们需要用单例模式去实例化出一个对象之前有没有别人在其他地方已经创建出这个类的对象了呢?
所以,代码优化一下,如下,判断之前再判断一次,如果这个类已经有实例了那咱们就不用无脑加个锁白白耗时了(咱们写的SingletonDoubleJudgeDemo只有在第一次的时候singletonDoubleJudgeDemo为空时,此时才会尝试实例化SingletonDoubleJudgeDemo类,这是第一次之前没有,所以肯定要加锁喽)
不好的写法五:(不好的写法四加了锁,但是还能再优化一下。我们只是在实例还没创建之前需要加锁操作)。但是如果这个实例在其他地方已经被其他人创建出来了,那咱们肯定就不用再加这个锁了呀(加锁很耗时的哟)。那咱们怎么知道当咱们需要用单例模式去实例化出一个对象之前有没有别人在其他地方已经创建出这个类的对象了呢?
- 所以,代码优化一下,如下,
判断之前再判断一次
,如果这个类已经有实例了那咱们就不用无脑加个锁白白耗时了(咱们写的SingletonDoubleJudgeDemo只有在第一次的时候singletonDoubleJudgeDemo为空时,此时才会尝试实例化SingletonDoubleJudgeDemo类,这是第一次之前没有,所以肯定要加锁喽)
public class SingletonDoubleJudgeDemo{
private SingletonDoubleJudgeDemo(){
}
private static SingletonDoubleJudgeDemo singletonDoubleJudgeDemo = new SingletonDoubleJudgeDemo();
private static Lock lock = new ReentrantLock();
public SingletonDoubleJudgeDemo getInstance(){
if(singletonDoubleJudgeDemo == null){
lock.lock();
try{
if(singletonDoubleJudgeDemo == null){
return singletonDoubleJudgeDemo;
}
} catch(Exception e){
e.printStack();
}finally{
lock.unlock();
}
}
}
}
接下来就是咋用,在哪用?
//用好的写法一和写法二,就是
......
Xxx xxx = Xxx.getInstance();
xxx就代表咱们由单例模式中得到的实例,现在就可以大张旗鼓的用xxx去调用Xxx类中的方法们咯。
从另一个从面上来讲,这些个设计模式咱们不管是实际写代码还是面试等,不仅会用到具体代码,包括纸面上的优缺点等一些特点咱们也是会用到的,so,捞唠呗。
单例模式的优点们:
- 一个对象需要频繁地 创建、销毁时,而且创建或销毁过程中的性能又无法优化,单例模式的优势就可以用一用了,快去改权限修饰符吧
- 单例模式只生成一个实例就相当于减少了系统的性能开销
- 当一个对象的产生需要 比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一 个单例对象,然后用永久驻留内存的方式,以后一直用这一个就行。
- 在Spring中,每个Bean默 认就是单例的,这样做的优点是Spring容器可以管理这些Bean的生命期,决定什么时候创建 出来,什么时候销毁,销毁的时候要如何处理,等等。如果采用非单例模式(Prototype类 型),则Bean初始化后的管理交由J2EE容器,Spring容器不再跟踪管理Bean的生命周期,就是说你管不了呀
- 单例模式只生成一个实例就相当于减少了系统的性能开销
- 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在 内存中,避免对同一个资源文件的同时写操作。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单 例类,负责所有数据表的映射处理
结合优点就可以大概知道咱们啥时候用、怎么用、用在哪里了:
- 在一个系统中如果出现多个对象就会出现不良反 应或者说面试人家要求设计这样一个类,要求一个类有且仅有一个对象,就可以上单例模式了
- 要求生成唯一序列号的环境就可以上单例模式了
- 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以 不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的,就可以上单例模式了
- 创建一个对象需要消耗的资源过多时,如要访问IO和数据库等资源,就可以上单例模式了节省一下资源
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当 然可以直接声明为static的方式)
单例模式的缺点们:
- 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途 径可以实现
另外呢,
- 如果要求一个类只能(一定数量的对象)比如产生两三个对象呢----
有上限的多例模式
public class SingletonDemoMultiObj{
//定义最多能产生的实例数量
private static int maxNumOfMultiObj = 2; //使用一个ArrayList来容纳,每个对象的私有属性
private static ArrayList<String> nameList=new ArrayList<String>(); //定义一个列表,容纳所有的实例
private static ArrayList<SingletonDemoMultiObj> multiObjList = new ArrayList<SingletonDemoMultiObj>();
private static int countNumOfMultiObj = 0; //产生所有的对象
static{
for(int i = 0; i < maxNumOfMultiObj; i++){
multiObjList.add(new SingletonDemoMultiObj(i+1));
}
}
private SingletonDemoMultiObj(){ }//传入皇帝名称,建立一个皇帝对象
private SingletonDemoMultiObj(String name){ nameList.add(name); }//随机获得一个皇帝对象
public static SingletonDemoMultiObj getInstance(){
Random random = new Random(); //随机拉出一个皇帝,只要是个精神领袖就成
countNumOfMultiObj = random.nextInt(maxNumOfMultiObj);
return multiObjList.get(countNumOfMultiObj);
}
}
这种情况就可以用在例如读取文件,我们可以在系 统启动时完成初始化工作,在内存中启动固定数量的reader实例,然后在需要读取文件时就 可以快速响应。