首页 > 代码库 > 设计模式----单例模式

设计模式----单例模式

创建型模式

单例模式是某个类只需要一个实例,保证一个类有且只有一个实例,并提供一个访问他的全局访问点。比如对于一个统一的数据库的访问,在整个项目中只使用同一个实例。对于这种情况有个比较好的例子,就是一夫一妻制。

比如某个男子需要娶个女子结婚,那么就有下面的程序:wife类,代表女子,husband类,代表男子

 

1
2
3
4
5
6
7
8
public class Wife {
    public void show() {
        System.out.println("the girl show themself");
    }
    public void marry() {
        System.out.println("the girl gets married");
    }
}
1
2
3
4
5
6
7
8
9
10
11
public class Husband {
 
    private Wife myWife = null;
    public void chooseGirl() {
                myWife = new Wife();
        myWife.show();
    }
    public void getMarry() {
        myWife.marry();
    }
}

在主程序里就可以实例化husband类,然后依次调用chooseGirl()和getmarry()方法,就可以正常运行。但是如果先调用getMarry()呢?由于wife在choose里面实例化的,这是就会报错,将husband类修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
public void chooseGirl() {
    if (myWife == null) {
        myWife = new Wife();
    }
    myWife.show();
}
public void getMarry() {
    if (myWife == null) {
        myWife = new Wife();
    }
    myWife.marry();
}

这样一来就可以保证一个丈夫只能有一个妻子了。而且两个函数没有调用的先后关系。

问题一:如果婆婆想要看儿媳妇呢?那就会有一个婆婆类,婆婆类里面又需要实例化一个wife类,这个实例化的wife类和husband的里面实例化的wife类是不同的,出现了多个实例化的问题,也就是婆婆看到的和儿子娶的不是同一个姑娘,这怎么行呢?这就需要修改wife类的代码,保证只有一个实例出现。wife类:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Wife {
    public static Wife wife;  //静态变量
    private Wife(){          //私有构造方法,是的外部无法访问
 
    }
    public static Wife getInstance(){  //静态方法来实例化
        if (wife == null) {
            wife = new Wife();
                }
        return wife;
    }
    public void show() {
        System.out.println("the girl show themself");
    }
    public void marry() {
        System.out.println("the girl gets married");
    }
}

将构造函数设为私有,声明静态变量wife和静态方法getinstance来进行实例化。在需要实例化的地方用Wife wife=Wife.getInstance()来获得实例,就可以保证大家看到的是同一个姑娘。

问题二:如果婚礼现场需要直播,怎么办?可以使用多线程技术,一个线程A作为实地的婚礼,在wife中调用marry方法,另一个线程B作为直播,同样在线程中调用marry的,那么怎么能确定getInstance得到的是同一个新娘呢?如果线程A先运行到此,这是wife为null,那么进入到分支实例化wife,在未完成实例化的时候,线程B也来到了分支内,此时wife依然为null,线程B又会进行一次实例化,最终还是产生了两个实例,就是两个不同的新娘。

解决:引入锁的概念,lock的作用就是构造出一块临界区来控制线程对代码的访问,确保每次只有一个线程运行到临界区的代码,其他线程运行到临界区时,将会一直等待直到前面的线程运行出临界区为止。

在wife中修改代码:在if前加锁

1
2
3
4
5
lock.lock();
if (wife == null) {
    wife = new Wife();
}
lock.unlock();

这样就能保证在A实例化之前B不会进行null的判断。这样虽然满足了功能的要求,可是每次调用的getInstance的时候都要进行加锁工作,这将会影响程序的性能。所以对其进行改良。

 

1
2
3
4
5
6
7
8
9
10
public static Wife getInstance(){  //静态方法来实例化
        if (wife == null) {
            lock.lock();
            if (wife == null) {
                wife = new Wife();
            }
            lock.unlock();
        }
        return wife;
    }

在临界区内在进行一次null的判断,只有wife为null的时候才会进入初始化。

单例模式本身的代码:

 

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
    public static Singleton singleton;  //静态变量
    private Singleton(){ 
        ^&&&                //私有构造方法,是的外部无法访问  
    }
    public static Singleton getInstance(){  //静态方法来实例化
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

客户端代码:

1
2
3
4
5
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
if(singleton1 == singleton2){
    System.out.printle("the teo object are the same one");
}

单例模式虽然很简单,但很实用,以下情况都适合实用:

1)当类只能有一个实例而且第三方可以从一个公共的访问点访问它时

2)当一个唯一的实例可以通过子类化来扩展,而且第三方需要在不更改代码的情况下就能实用一个扩展的实例时。

单例模式优点:

1)队唯一的实例做出访问控制

2)允许改变实例的个数,可以增加一个计数器来控制实例的个数,从而有双例模式,三利模式等。

 

总结:

单例并不是上面写的这么简单,里面涉及到很多的知识点,多线程、加锁、私有构造函数,静态构造函数,静态字段,readonly和const的区别等等。

向这里给出的单例版本就涉及到线程安全的问题,当2个请求同时方式这个类的实例的时候,可以会在同一时间点上都创建一个实例,虽然一般不会出异常错误,但是起码不是我们谈论的只保证一个实例了。每一个设计模式都值得我们深入的研究下去,有时间一定回头看看。

 

设计模式----单例模式