首次使用
该对象时才会创建推荐:方式一:枚举方式:枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
/**
* 枚举方式
*/
public enum Singleton {
INSTANCE;
}
public class Client {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance1 = Singleton.INSTANCE;
System.out.println(instance == instance1);
}
}
方式二:静态变量方式,也可以换在静态代码块内。
/**
* 饿汉式
* 静态变量创建类的对象
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance = new Singleton();
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return instance;
}
}
推荐:方式一:静态内部类方式
静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 static
修饰,保证只被实例化一次,并且严格保证实例化顺序
/**
* 静态内部类方式
*/
public class Singleton {
//私有构造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
说明
:第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全(jvm替我们加锁),也能保证 Singleton 类的唯一性。
方式二:双重检查锁+volatile关键字
双重检查锁是因为我们没有创建instance实例的时候才会加锁然后创建,当有这个实例的时候我们就不需要再拿到锁,在锁之前的if判断就跳出去然后直接放回已经创建好的对象。加volatile关键字是因为双重检测锁在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。用volatile
关键字可以保证可见性和有序性。
public class Singleton {
//私有构造方法
private Singleton() {}
private static volatile Singleton instance;
//对外提供静态方法获取该对象
public static Singleton getInstance() {
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
if(instance == null) {
synchronized (Singleton.class) {
//抢到锁之后再次判断是否为空
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
除了枚举方式。上面创建单例模式在反序列化
、反射
情况下会破坏单例模式,即创建的对象不是单例的。
反序列化破坏单例:
Singleton 是单例的,但是吧Singleton 序列化然后再反序列化后,得到的对象不是单例的。
public class Test {
public static void main(String[] args) throws Exception {
//往文件中写对象
//writeObject2File();
//从文件中读取对象
Singleton s1 = readObjectFromFile();
Singleton s2 = readObjectFromFile();
//判断两个反序列化后的对象是否是同一个对象
System.out.println(s1 == s2);
}
private static Singleton readObjectFromFile() throws Exception {
//创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt"));
//第一个读取Singleton对象
Singleton instance = (Singleton) ois.readObject();
return instance;
}
public static void writeObject2File() throws Exception {
//获取Singleton类的对象
Singleton instance = Singleton.getInstance();
//创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));
//将instance对象写出到文件中
oos.writeObject(instance);
}
}
上面代码运行结果是false
,表明序列化和反序列化已经破坏了单例设计模式。
原因:在反序列化时,每次将同一文件读入,它就会创建一个新的对象,导致不是单例。
解决办法:
在Singleton类中添加readResolve()
方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
public class Singleton implements Serializable {
//私有构造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 下面是为了解决序列化反序列化破解单例模式
*/
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
反射破坏单例:
public class Test {
public static void main(String[] args) throws Exception {
//获取Singleton类的字节码对象
Class clazz = Singleton.class;
//获取Singleton类的私有无参构造方法对象
Constructor constructor = clazz.getDeclaredConstructor();
//取消访问检查
constructor.setAccessible(true);
//创建Singleton类的对象s1
Singleton s1 = (Singleton) constructor.newInstance();
//创建Singleton类的对象s2
Singleton s2 = (Singleton) constructor.newInstance();
//判断通过反射创建的两个Singleton对象是否是同一个对象
System.out.println(s1 == s2);
}
}
上面代码运行结果是false
,表明反射已经破坏了单例设计模式
解决办法:
因为反射其实是拿到了类的私有构造器,然后来new出对象的,我们只需要在类的私有构造器中判断是不是第一次用构造器造对象,第一次的化让new,不是第一次的化就不让。
package com.itheima.pattern.singleton.demo8;
/**
* @version v1.0
* @ClassName: Singleton
* @Description: 静态内部类方式
* @Author: 黑马程序员
*/
public class Singleton {
private static boolean flag = false;
//私有构造方法
private Singleton() {
synchronized (Singleton.class) {
//判断flag的值是否是true,如果是true,说明非第一次访问,直接抛一个异常,如果是false的话,说明第一次访问
if (flag) {
throw new RuntimeException("不能创建多个对象");
}
//将flag的值设置为true
flag = true;
}
}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//提供公共的访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
问题导入:
需求:设计一个咖啡店点餐系统。
设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。
问题:在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的。
工厂模式最大的优点就是:解耦。
工厂类代码:
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffee;
}
}
测试代码:
package com.itheima.pattern.factory.simple_factory;
public class Client {
public static void main(String[] args) {
//创建咖啡店类对象
CoffeeStore store = new CoffeeStore();
Coffee coffee = store.orderCoffee("latte");
System.out.println(coffee.getName());
}
}
优缺点:
优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
静态工厂:
在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式
public class SimpleCoffeeFactory {
public static Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffe;
}
}
这样就不需要new SimpleCoffeeFactory了,直接可以调用createCoffee
package com.itheima.pattern.factory.static_factory;
public class CoffeeStore {
public Coffee orderCoffee(String type) {
//因为静态工厂,这里不需要new了,直接调用
Coffee coffee = SimpleCoffeeFactory.createCoffee(type);
//加配料
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
企业中应用:
简单工厂+配置文件解除耦合
可以通过简单工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。
第一步:定义配置文件
使用properties文件作为配置文件,名称为bean.properties
第二步:改进工厂类
public class CoffeeFactory {
private static Map<String,Coffee> map = new HashMap();
static {
Properties p = new Properties();
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
p.load(is);
//遍历Properties集合对象
Set<Object> keys = p.keySet();
for (Object key : keys) {
//根据键获取值(全类名)
String className = p.getProperty((String) key);
//获取字节码对象
Class clazz = Class.forName(className);
Coffee obj = (Coffee) clazz.newInstance();
map.put((String)key,obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Coffee createCoffee(String name) {
return map.get(name);
}
}
容器放在静态代码块中,只初始化一次,即只将配置文件中的类new一次放入map中,这样我们配置文件中配置的类就都会存在map中。
测试代码:
package com.itheima.pattern.factory.config_factory;
public class Client {
public static void main(String[] args) {
Coffee coffee = CoffeeFactory.createCoffee("american");
System.out.println(coffee.getName());
System.out.println("=============");
Coffee latte = CoffeeFactory.createCoffee("latte");
System.out.println(latte.getName());
}
}
在map中找key为american,就可以得到它的对象。
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
package com.itheima.pattern.factory.factory_method;
public class Client {
public static void main(String[] args) {
//创建咖啡店对象
CoffeeStore store = new CoffeeStore();
//创建对象
//CoffeeFactory factory = new AmericanCoffeeFactory();
CoffeeFactory factory = new LatteCoffeeFactory();
store.setFactory(factory);
//点咖啡
Coffee coffee = store.orderCoffee();
System.out.println(coffee.getName());
}
}