设计模式分类
- 创建型模式:
单例模式、工厂模式、抽象工程模式、建造者模式、原型模式 - 结构型模式
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式 - 行为型模式
模板方法模式、命令模式、迭代器模式、观察者模式、终结者模式、备忘录模式、揭示其模式、状态模式、策略模式、职责链模式、访问者模式
单例模式
- 核心作用
保证一个类只有一个实例,并且提供一个访问该实力的全局访问点 - 创建场景
Windows的任务管理器
Windows的回收站
项目中读取配置文件的类
网站的计数器
应用程序的日志应用
数据库连接池
操作系统文件系统
Application(servlet编程中会涉及)
Spring中,每个Bean默认是单例模式,Spring容器可以管理
servlet编程中,servlet也是单例
Spring MVC框架/struts1框架中,控制器对象也是单例 - 优点
由于单例模式只生成一个实例,减少了系统性能的开销,当一个对象的产生需要比较多的资源时,可以通过应用启动时直接产生一个实例对象,然后永久驻留内存的方式来解决
单例模式可以在系统设置全举办的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理 - 常见的5中单例模式实现方式
主要:
饿汉式(线程安全,调用效率高。但是,不能延时加载。)
懒汉式(线程安全,调用效率不高。但是,可以延时加载。)
其他:
双重检测锁式(由于JVM底层内部模型原因,偶尔会出现问题。不建议使用)
静态内部类式(线程安全,调用效率高。可以延时加载)
枚举单例(线程安全,调用效率高,但不能延时加载,并且可以天然的防止反射和反序列化漏洞!)
饿汉式(单例对象立即加载)
特点:线程安全,效率高,但不能延时加载
三点注意:
- 构造器私有
- 静态属性直接new,因为是饿汉式非常饿,刚开始就直接new
- 获取单例对象的静态方法 不需要synchroniezd
public class Demo02 {
private static Demo02 instance = new Demo02(); //饿汉式非常饿,上来就new,因为立即加载了,就没有延时加载的优势
private Demo02() {};
//这里不用设置线程同步 因为类加载的时候,是一个天然的线程安全模式,所以线程安全,不需要同步,显然效率高
public static Demo02 getInstance() {
return demo02;
}
}
懒汉式(单例对象 延时加载)
特点:线程安全,效率不高,可以延时加载(这就是懒)
三点注意:
- 构造器私有
- 静态属性不初始化
- 获取单例对象的方法 需要加synchronized
public class Demo03 {
private static Demo03 instance; //懒汉式,懒所以不立即加载
private Demo03() {};
//需要时才new,资源利用率高,但是并发效率不高,用了synchroniezd
public synchronized static Demo03 getInstance() {
if (instance == null) {
instance = new Demo03();
}
return instance;
}
}
双重检测锁
因为编译器优化和jvm内存模型问题,不建议使用,在 Java 5.0 之后,使用 volatile 来修饰 singleInstance 实例,就不会产生指令重排序的情况,这样 DCL 也就可以正常工作了。
但因为有了更加方便与安全的替代方式,DCL 也没有什么特别的优势,便被废弃了。
public class Demo04 {
private static Demo04 instance;
private Demo04() {}
public static Demo04 getInstance() {
if (instance == null) {
Demo04 demo04;
synchronized (Demo04.class) {
demo04 = instance;
if (demo04 == null) {
synchronized (Demo04.class) {
demo04 = new Demo04();
}
}
instance = demo04;
}
}
return instance;
}
}
上面代码是视频里面的 因为没有讲解我看不懂
然后用volatile实现的双重检测锁
public class Demo05 {
//加上volatile可以解决编译器的优化问题和cpu缓存
private static volatile Demo05 instance;
private Demo05() {}
public static Demo05 getInstance() {
if (instance == null) {
synchronized (Demo05.class) {
if (instance == null) {
instance = new Demo05();
}
}
}
return instance;
}
}
静态内部类(也是一种懒加载方式)
特点:线程安全,调用效率高。可以延时加载
要点:
构造器私有
外部类没有static属性,就不会像饿汉式那样立即加载对象
只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。instance是static final类型,保证内存中只有一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
兼备了并发高效调用和延时加载的优势
//加载类是不回去加载静态内部类,只有调用方法时才会加载,所以是懒加载,也就能延时加载
public class Demo06 {
private Demo06() {}
private static class ClassInstance {
private static final Demo06 instance = new Demo06();
}
public static Demo06 getInstance() {
return ClassInstance.instance;
}
}
枚举实现
优点:实现简单,枚举本身就是单例模式,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞
缺点:无延迟加载
//枚举类
public enum Demo07 {
//枚举元素,本身就是单例,可以直接类名调用
INSTANCE;
//枚举类还有可以自己的操作
public void operationMethod() {
//代码
}
}
如何防止反射和反序列漏洞
- 解决反射
在私有构造方法中加入if (instance != null) {throw new RuntimeException}
public class Demo02 {
private static Demo02 instance = new Demo02(); //饿汉式非常饿,上来就new
private Demo02() {
if (instance != null) {
throw new RuntimeException();
}
};
//这里不用设置线程同步 因为类加载的时候,是一个天然的线程安全模式
public static Demo02 getInstance() {
return instance;
}
}
class Main01 {
public static void main(String[] args) {
Demo02 demo02_01 = Demo02.getInstance();
Demo02 demo02_02 = Demo02.getInstance();
System.out.println(demo02_01);
System.out.println(demo02_02);
try {
Class<Demo02> cla = (Class<Demo02>)Class.forName("单例设计模式.Demo02");
Constructor<Demo02> constructor = cla.getDeclaredConstructor(null);
constructor.setAccessible(true);
Demo02 demo02_03 = constructor.newInstance();
Demo02 demo02_04 = constructor.newInstance();
System.out.println(demo02_03);
System.out.println(demo02_04);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
- 反序列化时需要一个方法 private Object readResolve() throws ObjectStreamException {return instance}
public class Demo03 implements Serializable {
private static Demo03 instance; //懒汉式,懒所以不立即加载
private Demo03() {};
//需要时才new,资源利用率高,但是并发效率不高,用了synchroniezd
public synchronized static Demo03 getInstance() {
if (instance == null) {
instance = new Demo03();
}
return instance;
}
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
class Main02 {
public static void main(String[] args) {
Demo03 demo03_01 = Demo03.getInstance();
Demo03 demo03_02 = Demo03.getInstance();
System.out.println(demo03_01);
System.out.println(demo03_02);
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C:/users/night/desktop/a.txt"));
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("C:/users/night/desktop/a.txt"));
objectOutputStream.writeObject(demo03_01);
Demo03 demo03_03 = (Demo03) objectInputStream.readObject();
System.out.println(demo03_03);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
多线程模式下测试单例模式各个方法的效率
用countDownLatch测试方法的效率
public class Demo08 {
public static void main(String[] args) {
long start = System.currentTimeMillis();
int threadNum = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum; i++) {
new Thread(() -> {
for (int j = 0; j < 1000000; j++) {
Object instance = Demo07.INSTANCE;
}
countDownLatch.countDown();
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
如何选用?
单例对象 占用资源少,不需要延时加载
枚举式好于饿汉式
单例对象 占用资源大,需要延时记载
静态内部类好于懒汉式