目录
1、看懂UML类图和时序图
- 车的类图结构为<< abstract >>,表示车是一个抽象类;
- 它有两个继承类:小汽车和自行车;它们之间的关系为实现关系,使用带空心箭头的虚线表示;
- 小汽车为与SUV之间也是继承关系,它们之间的关系为泛化关系(继承非抽象类),使用带空心箭头的实线表示;
- 小汽车与发动机之间是组合关系,使用带实心箭头的实线表示;
- 学生与班级之间是聚合关系,使用带空心箭头的实线表示;
- 学生与身份证之间为关联关系,使用一根实线表示;
- 学生上学需要用到自行车,与自行车是一种依赖关系,使用带箭头的虚线表示;
2、单例模式
2.1 定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
2.2 单例模式几个好处
- 省略创建对象所花费的时间,减少系统开销。
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
2.3 为什么不使用全局变量确保一个类只有一个实例呢?
只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。但是,如果说这个对象非常消耗资源,而且程序某次的执行中一直没用,这样就造成了资源的浪费。利用单例模式的话,我们就可以实现在需要使用时才创建对象,这样就避免了不必要的资源浪费。
- 消耗资源,资源浪费。
- 增加程序调试和维护的困难。
2.4 单例的模式的实现
- 饿汉方式:指全局的单例实例在类装载时构建
- 懒汉方式:指全局的单例实例在第一次被使用时构建。
2.4.1 饿汉式(线程安全)
JVM 在加载这个类是就立马创建此类唯一的实例。
public class Singleton{
//在静态初始化器中创建单例实例,保证线程安全
private static Singleton uniqueInstance = new Singleton();
//Singleton 类只有一个构造方法,并且是被 private 修饰的, 所以无法通过 new 构建对象实例。
private Singleton(){}
public static Singleton getInstance(){
return uniqueInstance;
}
}
2.4.2 懒汉式
2.4.2.1 非线程安全式
public class Singleton(){
private static Singleton uniqueSingleton;
private Singleton(){}
public static getInsatnce(){
if(uniqueSingleton == null){
uniqueSingleton = new Singleton();
}
return uniqueSingleton;
}
}
在单例实例第一次使用时构建,而不是在 JVM 在加载类时就创建。但是上面线程不安全,多个线程同时访问 getInstance() 方法时会出现问题。
2.4.2.2 线程安全方式
2.4.2.2.1 加锁
public class Singleton(){
private static Singleton uniqueSingleton;
private Singleton(){}
public synchronized Singelton getInstance(){
if(uniqueSingleton == null){
uniqueSingleton = new Singleton();
}
return uniqueSingleton;
}
}
缺点:耗性能,会阻塞。
2.4.2.2.2 双重检查加锁
volatile : 保证不将该关键词修饰的变量缓存在线程的内存中,而是直接访问主内存(共享内存)中的变量
public class Singleton(){
//volatile保证当 single 变量被初始化成 Singleton 实例时,多个线程可以正确处理 singleton 变量
private volatile static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
//检查实例,如果不存在就进入同步代码块
if(singleton == null){
//只有第一次才执行下面语句
synchronized(Singleton.class){
if(singleton == null){
//进入同步代码块后,再检查一次,如果仍是null,才创建实例
singleton = new Singleton();
}
}
}
return singleton;
}
}
2.4.3 登记式/静态内部类
注意:被 final 修饰的静态字段不会主动初始化,在调用该字段时才会初始化。
public class Singleton(){
private static class SingletonHolder(){
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInsatance(){
return SingletonHolder.INSTANCE;
}
}
2.4.4 枚举方式
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。 它更简洁,自动支持序列化机制,绝对防止多次实例化 (如果单例类实现了Serializable接口,默认情况下每次反序列化总会创建一个新的实例对象,关于单例与序列化的问题可以查看这一篇文章《单例与序列化的那些事儿》),同时这种方式也是《Effective Java 》以及《Java与模式》的作者推荐的方式。
public enum Singleton{
//定义一个枚举元素,就是单例
INSTANCE;
public void doSomething(){
System.out.println("枚举方式实现单例");
}
}
使用方式:
public class ESTest {
public static void main(String[] args) {
Singleton singleton = Singleton.INSTANCE;
singleton.doSomeThing();//output:枚举方法实现单例
}
}
2.4.5 单例不一定实现实例的唯一性(除了枚举方式)
- 反射可以破坏单例模式
- 序列化会破坏单例
- 原因:序列化会通过反射调用无参数的构造方法创建一个新的对象。
- 解决方法:在双重加锁实现的单例中,在 Singleton 中定义 readResolve 方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。