单例设计模式的意思就是,确保一个类只能有一个对象。任何别的方法什么的对该类的操作全是对这一个对象的操作。如同我们电脑上的回收站。无论那个盘都有一个回收站的空间。但都是同一个。可以在回收站里全删除了。Jvm里面的GC也是一个意思。
单例设计模式有两种情况。
一、懒汉式
- 声明一个私有的静态变量
- 构造器私有化
- 创建一个对外的公有静态方法访问该变量,如果变量不存在,创建该对象。如果存在则返回这个私有的静态变量。这样就能保证外部在使用该类的对象时只会用同一个。
基本的懒汉式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Jvm{ //声明一个私有的静态变量 private static Jvm instance; //构造器私有化 private Jvm(){ } //创建一个对外的公有静态方法访问该变量,如果变量不存在,创建该对象 public static Jvm getJvm(){ if(null==instance){ instance=new Jvm(); } return instance; } }
|
这样在执行下列语句
1 2 3 4
| Jvm jvm1=Jvm.getJvm(); Jvm jvm2=Jvm.getJvm(); System.out.println(jvm1); System.out.println(jvm2);
|
他们得到的对象都是一样的了。地址一样即为同一个对象。
1 2
| synTest.Jvm@15db9742 synTest.Jvm@15db9742
|
但是这样的懒汉式却是不安全的。如果是多线程来使用这个类,就会存在延迟。可能会产生多个对象。
这里我们放大他的延迟时间更容易的得到错误的结果。
修改上面的Jvm类。给他设置一个延迟时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Jvm1{ private static Jvm1 instance;
private Jvm1() { } public static Jvm1 getJvm(long time) { if (null == instance) { try { Thread.sleep(time); //这里由外部给定延迟时间 } catch (InterruptedException e) { e.printStackTrace(); } instance = new Jvm1(); } return instance; } }
|
创建使用这个Jvm1的线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class JvmThread extends Thread{ private long time; public JvmThread() { } //为方便查看,这里构造器也传个name,给这个线程设置名字。 public JvmThread(long time,String name){ this.time=time; //父类继承了私有的name,直接设置即可 super.setName(name); } public void run() { System.out.println(Thread.currentThread().getName()+"创建了:"+Jvm1.getJvm(this.time)); //这里直接在线程里面输出地址 } }
|
执行下列语句
1 2 3 4
| JvmThread jth1=new JvmThread(500,"a"); JvmThread jth2=new JvmThread(1000,"b"); jth1.start(); jth2.start();
|
上面两个线程都使用了Jvm1类。构造了。按照单例模式的要求,对象地址应该是一样的。但结果如下
1 2
| a创建了:synTest.Jvm1@43e8772d b创建了:synTest.Jvm1@20b708e4
|
所以这里就需要用到同步措施,使线程变得安全。
比较简单的方法是直接使用同步方法,或同步块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Jvm1{ private static Jvm1 instance;
private Jvm1() { } //这个方法改成同步方法即可 public static synchronized Jvm1 getJvm1(long time) { if (null == instance) { try { Thread.sleep(time); //这里由外部给定延迟时间 } catch (InterruptedException e) { e.printStackTrace(); } instance = new Jvm1(); } return instance; } }
|
或者同步块
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static Jvm1 getJvm1(long time) { synchronized(Jvm1.class){ if (null == instance) { try { Thread.sleep(time); //这里由外部给定延迟时间 } catch (InterruptedException e) { e.printStackTrace(); } instance = new Jvm1(); } return instance; } }
|
但是这样子设置会出现一个问题,每一个线程调用这个方法都得等待,即使已经有对象了,还需要等待。
稍作改进,人称double check
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static Jvm1 getJvm1(long time) { if(null==instance){ synchronized(Jvm1.class){ if (null == instance) { try { Thread.sleep(time); //这里由外部给定延迟时间 } catch (InterruptedException e) { e.printStackTrace(); } instance = new Jvm1(); } } } return instance; }
|
这样子,如果线程调用时,已经有对象的时候,就可以直接返回啦。不需要进去等待。而如果多个线程同时在没有对象时调用。也会进入那个synchronized块。不会生成多个对象。
二、饿汉式
1.构造器私有化
2.声明私有的静态属性,同时创建对象
3.对外提供访问属性的静态方法
饿汉式由于对象直接构建了,所以调用都是调用那个对象,不存在线程安全问题了。
饿汉式也有普通的和完善的。
基本饿汉式
1 2 3 4 5 6 7 8 9 10 11 12
| class MyJvm{ //声明私有的静态变量,同时构建对象 private static MyJvm instance=new MyJvm(); //构造器私有 private MyJvm() { } public static MyJvm getMyJvm(){ return instance; } }
|
这里有一点不好的就是,在声明MyJvm时候,就构造了他的对象。如果MyJvm里面还有其他的静态方法的话,并且也只使用其他方法,那么那个对象的必要性就没有了。所以做如下改进。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class MyJvm2{ //这里定义一个静态内部类。类是在调用的时候才会加载的。 //当调用了下面的getMyJvm2方法时,才会用到MyJvm2Holder类,才会加载这个类。就会有那个对象了。 private static class MyJvm2Holder{ private static MyJvm2 instance=new MyJvm2(); } private MyJvm2() { } public static MyJvm2 getMyJvm2(){ return MyJvm2Holder.instance; } }
|