多例:只是单例的一种延伸 不必过于在意各种模式的名字,重要的是学会融会贯通,把生产的car放到集合中 类似JDBC 的连接池 把连接对象放到池中
多例模式特点:
1. 多例类可以有多个实例
2. 多例类必须自己创建自己的实例,并管理自己的实例,和向外界提供自己的实例
package com.pers.hoobey;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Car1 {
private static Car1 car1 = new Car1();
private static Car1 car2 = new Car1();
private static List<Car1> list = new ArrayList<Car1>();//用于存放多个实例的car
private static final int maxCount =2;//最多的实例数
static{
list.add(car1);
list.add(car2);
}
private Car1(){}//私有构造方法 避免外部创建实例
/*
* @description 指定拿取某一个实例
*/
public static Car1 getInstance(int index){
return list.get(index);
}
//随机拿取实例
public static Car1 getInstance(){
Random random = new Random();
int current = random.nextInt(maxCount);
return list.get(current);
}
public void run(){
System.out.println("奔跑中的车.....");
}
}
顺便总结一下 普通工厂模式 工厂方法模式和抽象工厂模式的区别:
简单工厂是用来生产”东西“的,那任何”东西“的子类,比如汽车,自行车,轮船,洗发水都是可以被生产的,但此处简单工厂的压力太大了啊,任何”东西“的子类都可以被生产,负担太重,所以一般对简单工厂类也有种称呼,叫”上帝类“。
而工厂方法模式就很好的减轻了工厂类的负担,把某一类/某一种东西交由一个工厂生产,同时增加某一类”东西“并不需要修改工厂类,只需要添加生产这类”东西“的工厂即可,使得工厂类符合开放-封闭原则。
对于”东西“的分类,有时候不能光是横向的分类,从另一个角度也是可以分类的,不知道这句话的意思能不能懂,打个比方:汽车可以根据品牌分为奔驰、奥迪,也可以根据类别分为普通三厢车和SUV车,如果用工厂方法来描述的话,奔驰车工厂有一个方法即生产奔驰车,奥迪车工厂有一个方法生产奥迪车,但在有多重分类的情形下,这样写已经不够用,不符合实际了,这个时候需要用到抽象工厂模式,即奥迪车工厂有两个方法,一个方法是生产普通三厢奥迪车,另一个方法是生产SUV奥迪车。奔驰车工厂有两个方法,一个方法是生产普通三厢奔驰车,另一个方法是生产SUV奔驰车。
上面即工厂方法模式和抽象工厂模式的应用场景,因为这两者很像,所以概念上不容易区分,可以这么说,工厂方法模式是一种极端情况的抽象工厂模式,而抽象工厂模式可以看成是工厂方法模式的一种推广。
再说下抽象工厂模式,此处的抽象工厂接口应该是有两个方法,一个是生成普通三厢车,一个是生产SUV车,可以说,生产的”东西“已经被限定住了,因此你不能生产某品牌汽车外的其他”东西“,因而可以理解成使用抽象工厂模式不能新增新的”东西“(在简单工厂和工厂方法中理论上都是可以新增任意”东西“的)
下面分析一下懒汉模式下的单例模式
作为一个单例,我们首先要确保的就是实例的“唯一性”,有很多因素会导致“唯一性”失效,它们包括:多线程、序列化、反射、克隆等,更特殊一点的情况还有:分布式系统、多个类加载器等等。其中,多线程问题最为突出。为了提高应用的工作效率,现如今我们的工程中基本上都会用到多线程;目前使用单线程能轻松完成的任务,日复一日,随着业务逻辑的复杂化、用户数量的递增,也有可能要被升级为多线程处理。所以任何在多线程下不能保证单个实例的单例模式,我都认为应该立即被弃用。
在只考虑一个类加载器的情况下,“饿汉方式”实现的单例(在系统运行起来装载类的时候就进行初始化实例的操作,由JVM虚拟机来保证一个类的初始化方法在多线程环境中被正确加锁和同步,所以)是线程安全的,而“懒汉”方式则需要注意了,先来看一种最简单的“懒汉方式”的单例:
这种写法只能在单线程下使用。如果是多线程,可能发生一个线程通过并进入了 if (singleton == null)
判断语句块,但还未来得及创建新的实例时,另一个线程也通过了这个判断语句,两个线程最终都进行了创建,导致多个实例的产生。所以在多线程环境下必须摒弃此方式。
除了多并发的情况,实现单例模式时另一个重要的考量因素是效率。前述的“懒汉方式”的多线程问题可以通过加上 synchronized
修饰符解决,但考虑到性能,一定不要简单粗暴地将其添加在如下位置:
上述方式通过为 getInstence()
方法增加 synchronized
关键字,迫使每个线程在进入这个方法前,要先等候别的线程离开该方法,即不会有两个线程可以同时进入此方法执行 new Singleton()
,从而保证了单例的有效。但它的致命缺陷是效率太低了,每个线程每次执行 getInstance()
方法获取类的实例时,都会进行同步。而事实上实例创建完成后,同步就变为不必要的开销了,这样做在高并发下必然会拖垮性能。所以此方法虽然可行但也不推荐。那我们将同步方法改为同步代码块是不是就能减少同步对性能的影响了呢:
但是这种同步却并不能做到线程安全,同最初的懒汉模式一个道理,它可能产生多个实例,所以亦不可行。我们必须再增加一个单例不为空的判断来确保线程安全,也就是所谓的“双重检查锁定”(Double Check Lock(DCL))方式:
此方法的“Double-Check”体现在进行了两次 if (singleton == null)
的检查,这样既同步代码块保证了线程安全,同时实例化的代码也只会执行一次,实例化后同步操作不会再被执行,从而效率提升很多(详细比较见附录 1)。
双重检查锁定(DCL)方式也是延迟加载的,它唯一的问题是,由于Java 编译器允许处理器乱序执行,在JDK版本小于1.5时会有DCL失效的问题(原因解释详见附录 2)。当然,现在大家使用的JDK普遍都已超过1.4,只要在定义单例时加上1.5及以上版本具体化了的volatile关键字,即可保证执行的顺序,从而使单例起效。所以 DCL 方式是推荐的一种方式。