0%

单例设计模式

单例设计模式的意思就是,确保一个类只能有一个对象。任何别的方法什么的对该类的操作全是对这一个对象的操作。如同我们电脑上的回收站。无论那个盘都有一个回收站的空间。但都是同一个。可以在回收站里全删除了。Jvm里面的GC也是一个意思。

单例设计模式有两种情况。

一、懒汉式

  1. 声明一个私有的静态变量
  2. 构造器私有化
  3. 创建一个对外的公有静态方法访问该变量,如果变量不存在,创建该对象。如果存在则返回这个私有的静态变量。这样就能保证外部在使用该类的对象时只会用同一个。

基本的懒汉式

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;
}
}