马上要过年咯,提前祝大家新年快乐~现在已经可以购买春运的票了~所以我们用一个购票的例子来引出我今天要介绍的单例模式,如果介绍有误还望指出~
每次抢票的时候都有一大堆人等着放票然后瞬间抢空,系统是怎么知道那张票该属于谁,票的余量又是怎么控制的呢?
什么是单例模式
单例模式是一种常用的软件设计模式,用一句话来解释就是:应用该模式的类任何时候只存在一个实例。
贴一个简单的购票代码:
public class TicketDispenser {
private int count;
public TicketDispenser() {
count = 1000;
}
public void getTicket() {
if (!isEmpty()) {
count--;
}
}
public boolean isEmpty() {
return count == 0;
}
}
代码中构造器以public修饰意味着在程序中可以随意创建出这样一个对象,不符合单例模式的概念,而对于票的数量应该只能存在一个对象去管理,若存在多个对象会出现很严重的问题,你不想很多人拿着一样的票跟你抢座位对吧?改!
构造器改为私有,提供一个公共方法创建实例
由于构造器变为私有,我们没有别的办法去创建一个实例,所以我们需要提供一个公共的创建实例的方法,修改后的代码:
private static TicketDispenser uniqueTicketDispenser;
private TicketDispenser() {
count = 1000;
}
public static TicketDispenser getUniqueTicketDispenser() {
if (uniqueTicketDispenser == null) {
uniqueTicketDispenser = new TicketDispenser();
}
return uniqueTicketDispenser;
}
上面的代码在单线程中可以说是完美的运行了,但是在多线程中还是会出现同时存在两个或多个实例,为了避免文章又臭又长(虽然已经有点这意思了。。。)大家可以自行去测试~
uniqueTicketDispenser = new TicketDispenser();
并不是一个原子型的操作,它应该是三个步骤,声明uniqueTicketDispenser ,创建一个TicketDispenser对象,将该对象赋值给uniqueTicketDispenser ;这意味着在多线程的时候,uniqueTicketDispenser 还未被赋值时已经被返回(这时候的uniqueTicketDispenser 是null),才会导致各种的错误,我们可以用synchronized关键字来添加一个锁,当某个线程进入程序还未完成操作时其他线程禁止进入来避免出现上述问题。
下面继续编写改进的代码:
很简单,就加一个关键字,
public static synchronized TicketDispenser getUniqueTicketDispenser()
此处还是有缺陷的,如果你的程序需要大量调用该方法并且你注重该方法的性能,在方法处加锁是一个十分耗性能的操作,修改!
private static volatile TicketDispenser uniqueTicketDispenser;
public static TicketDispenser getUniqueTicketDispenser() {
if (uniqueTicketDispenser == null) {
synchronized (TicketDispenser.class) {
if (uniqueTicketDispenser == null) {
uniqueTicketDispenser = new TicketDispenser();
}
}
}
return uniqueTicketDispenser;
}
使用双重检验锁机制,其实大部分时候uniqueTicketDispenser 应该是存在的,第一次判断可使大部分的调用直接跳过里面的代码,而第二次的创建加上了锁使得程序变得安全,volatile 的含义是禁止jvm的指令重排(可以看作是将`uniqueTicketDispenser = new TicketDispenser();“变成一个原子型的操作),保证多线程中正确处理uniqueTicketDispenser变量。到此,这个单例模式算得上一个比较成功的例子了。
其他实现方式
饿汉式:
private static TicketDispenser ticketDispenser = new TicketDispenser();
直接在静态初始化器中创建,由于是静态的,在类加载的时候只会加载一次,所以它是线程安全的。缺点是不能做到按需加载,可能会造成资源浪费。
枚举:
在effective java 中看到推荐的方式;首先,在枚举中构造方法被限制为private,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,这意味着只能被实例化一次,无疑这也是线程安全的。
public enum EnumUniqueTicketDispenser {
INSTANCE;
private TicketDispenser instance;
EnumUniqueTicketDispenser() {
instance = new TicketDispenser();
}
public TicketDispenser getEnumUniqueTicketDispenser() {
return instance;
}
}
到这里,我要说的大概说完了。。想到别的了再补充吧,还在网上看到过登记式单例,但是我认为目前知道的已经够了,等到我觉得需要时再写吧~大家有兴趣的可以去看看~如果大家觉得写的不错麻烦点个赞给点动力~写的有问题也希望大家能及时提出来哈,祝大家新年快乐,都能尽早抢到票回家~