单例模式&多例模式
来源:互联网 发布:整理相册的软件 编辑:程序博客网 时间:2024/04/29 12:10
初识单例模式
单例模式规定一个类仅有一个实例,并提供一个访问它的全局访问点。就是说单例类必须满足一下几点
- 单例类只有一个实例
- 单例类必须自己创建这个实例
- 必须向整个系统提供这个实例
单例模式作为众多设计模式中最简单的设计模式之一,原理非常简单,下面主要介绍单例模式的几种不同的实现方式。
实现方式
饿汉式
public class HungarySingleton { private HungarySingleton(){}; // 静态变量 private static final HungarySingleton instance = new HungarySingleton(); // 必须使用static,不加就需要调用方创建实例然后调用,而构造函数是私有的 public static HungarySingleton getInstance(){ return instance; }}
之所以成为“饿汉式”是因为在加载类的时候已经完成了静态变量的初始化。这个方式的优点就是不用考虑线程安全问题,缺点也很明显,如果一个对象初始化需要很长的时间而又没有被调用,就造成了资源浪费。
懒汉式
又称为延迟加载,在需要调用时候才会去创建对象。
public class LazySingleton { private LazySingleton() {} // 使用静态变量记录类的唯一实例 private static LazySingleton instance = null; public static LazySingleton getInstance(){ if(instance == null){ instance = new LazySingleton(); } return instance; }}
以上代码在单线程环境下没有任何问题,但是在多线程环境下就会出现创建多个实例的问题,解决方法非常简单,使用同步即可,可以使用同步方法或同步代码块,又因为同步比较消耗性能,在判断instance为null时再使用同步代码块,改进后的代码如下
public class LazySingleton { private LazySingleton() {} // 使用静态变量记录类的唯一实例 private static LazySingleton instance = null; public static LazySingleton getInstance(){ if(instance == null){ // 1 // 同步代码块 synchronized (LazySingleton.class) { // 2 instance = new LazySingleton(); // 3 } } return instance; }}
同步是非常浪费性能的,因为一次只能有一个线程执行,其它线程等待,所有在同步之前先判断instance是否为空,然后在同步。分析一下以上代码:有两个线程A和线程B,都是首次执行,假设现在线程A和B都执行到“代码1处”,继续向下执行,假设线程A获得锁执行同步代码块,线程B在“代码2处”等待,线程A执行到“代码3处”创建对象退出同步代码块并释放锁,此时已经创建了对象。然后线程B获得锁,执行“代码3处”再次创建对象,这就导致出现了多个对象。解决方法也很简单,在同步代码块内部做一次非空判断即可,这也是所谓的“双重检测”。
双重检测
public class DoubleSingleton { private volatile static DoubleSingleton instance; private DoubleSingleton(){} public static DoubleSingleton getInstance(){ if(instance == null){ synchronized (DoubleSingleton.class) { if (instance == null) { instance = new DoubleSingleton(); } } } return instance; } public static void main(String[] args) { ExecutorService pool = Executors.newCachedThreadPool(); for (int i = 0; i < 10000; i++) { pool.submit(new Runnable() { @Override public void run() { System.out.println(DoubleSingleton.getInstance().hashCode()); } }); } pool.shutdown(); }}
需要注意instance变量需要使用volatile关键词修饰,来保证在多线程下能够正确的处理instance变量,必须在jdk5之后才可以使用volatile关键字。假如没有volatile关键字修饰的话,会出现一个问题:在Java编译器中,JVM会对代码进行优化,也就是重排序,也就是说DoubleSingleton类的初始化和instance变量赋值顺序 不可预料,假如一个线程在没有同步化的条件下读取instance,并调用该对象的方法,可能对象的初始化还没有完成,从而造成程序错误。
一直没有模拟出来没有volatile关键字修饰程序会出现什么错误,希望看到的小伙伴不吝赐教!!
可以看到使用饿汉式实现单例模式优点就是不用考虑多线程,缺点是比较占用内存。懒汉式方式比较繁琐,volatile也会降低程序的性能。那么有没有一种结合两者优点的方式呢?可以使用静态内部类来完成
静态内部类
public class InnerSingleton { private InnerSingleton(){} private static class Singleton{ private static InnerSingleton single = new InnerSingleton(); } public static InnerSingleton getInstance(){ return Singleton.single; }}
只有在调用getInstance()方法时才会创建对象,既保证了线程安全,又保证了延迟加载。
枚举方式
《Effective Java》第三条:Java5之后,可以使用枚举实现单例,可以防止多次序列化以及反射攻击,同时又非常简洁,可以说是单例模式最佳的实现方式。
public enum EnumSingleton { INSTANCE; public void print(){ System.out.println("I am EnumSingleton"); }}
实现多例模式
下面对单例模式进行改造,使用缓存的思想模拟实现“多例模式”,既程序中一个类的实例对象可以存在有限多个(比如说3个),和单例模式唯一的区别就是程序中可以有多个实例对象
public class SomeSingleton { private SomeSingleton() {} // 存储创建的实例(模拟缓存) private static Map<Integer, SomeSingleton> map = new HashMap<>(3); // 计数器从1开始 private volatile static int num = 1; // 控制最多有三个实例 private static final int MAX_NUM = 3; // 锁 final static ReentrantLock lock = new ReentrantLock(); public static SomeSingleton getInstance() { lock.lock(); try { // 从缓存中取出实例 SomeSingleton instance = map.get(num); if (instance == null) { instance = new SomeSingleton(); // 计数器作为map的key map.put(num, instance); } // 计数器+1 num++; if (num > MAX_NUM) { // 重置计数器 num = 1; } return instance; } finally { lock.unlock(); } } public static void main(String[] args) { final Set<Integer> set = new HashSet<>(); ExecutorService pool = Executors.newCachedThreadPool(); for (int i = 0; i < 10000; i++) { pool.submit(new Runnable() { @Override public void run() { // System.out.println(getInstance().hashCode()); set.add(getInstance().hashCode()); } }); } System.out.println(set.size()); pool.shutdown(); }}
不管有多少个线程调用,都只会创建3个实例对象,完成了控制有限过个实例对象。
小结
单例模式的实质就是控制实例对象在程序中的数量有且仅有一个,并且只能自己创建。实现单例可以使用“静态内部类”和“枚举”方式,不过“静态内部类”需要注意反射攻击以及序列化破坏。
JDK中的单例模式
java.lang.Runtime
java.text.NumberFormat
Spring中
在Spring中默认的bean都是单例的
- 单例模式与多例模式
- 单例模式与多例模式
- 单例模式和多例模式
- 单例模式和多例模式
- 单例模式与多例模式
- Java单例模式、多例模式
- 单例、多例模式&&工厂模式
- 单例模式&多例模式
- 设计模式------单例模式
- 设计模式------单例模式
- 设计模式-单例模式
- 设计模式 - 单例模式
- 设计模式---单例模式
- 设计模式---单例模式
- PHP模式-单例模式
- 【设计模式】单例模式
- 设计模式-单例模式
- 设计模式----单例模式
- android控件08---Spinner
- 【Java并发】
- gitlab 使用现有 nginx 服务器
- 欢迎使用CSDN-markdown编辑器
- Oracle数据库相关概念
- 单例模式&多例模式
- LeetCode 376. Wiggle Subsequence
- 记一次 gitlab 与老的 nginx 冲突处理
- 远程访问linux图形化界面
- Ubuntu 16.04 安装深度学习环境 GPU 加速版
- 当excel表格单元格的格式是日期格式非文本格式的时候,phpexcel 应该要这样处理(thinkphp3.2)
- 【Gerrit】Linux+Gerrit+Nginx配置全过程
- 文章标题
- Nginx配置反向代理访问 Gitlab