第二章创建模式—单例设计模式
文章目录
- 单例模式的结构
- 如何控制只有一个对象呢
- 怎么设计这个类的内部对象
- 外部怎么访问
- 单例模式的主要有以下角色
- 单例模式的实现
- 饿汉式 1:静态变量
- 饿汉式 2:静态代码块
- 懒汉式 1:线程不安全
- 懒汉式 2:线程安全—方法级上锁
- 懒汉式 3:双重检查锁🚀
- 懒汉式 4:静态内部类🚀
- 饿汉式 3:枚举🚀
- 存在的问题
- 序列化破坏单例
- 反射破坏单例
- JDK 源码 - Runtime 类
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 所谓的单例模式保证某个类在程序中有且只有一个对象,类比现实生活中的地球类,只有一个地球对象
单例模式的结构
如何控制只有一个对象呢
- 我们创建一个对象是通过该类的构造方法进行创建对象
- 我们不能让外部进行创建对象,不然谁都能创建对象,那么就保证不了单例,那么我们的构造方法必须是private修饰,来控制对象的创建,也就是私有的构造方法
- 外部不能创建对象,那么这个创建唯一的对象的任务也就会在我们单例类来进行实现
怎么设计这个类的内部对象
- 首先我们知道在外部是创建不了对象的,所以这个对象的类型肯定不能是成员的,必须是静态的,通过类调用
- 因为成员变量的属性是通过我们的对象进行赋值,我们只有一个对象,如果是成员变量,就先要有对象才能赋值,而赋值了才有那个唯一的对象,就出现了死循环
外部怎么访问
- 一般属性我们用private进行修饰,为了实现其封装性
- 直接通过公开的方法获取这个唯一的对象
单例模式的主要有以下角色
- 单例类。只能创建一个实例的类
- 访问类。使用单例类
单例模式的实现
单例设计模式分类两种:
- 饿汉式:类加载就会导致该单实例对象被创建
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉式 1:静态变量
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton=new Singleton();//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton() {return singleton;}
}
- 因为singleton是类成员变量,在我 Singleton类加载过程中的初始化过程中进行创建对象赋值,因为只会执行一次,所以是天然的线程安全
- 缺点:singleton 对象是随着类的加载而创建的,如果该对象很大,却一直没有使用就会造成内存的浪费
饿汉式 2:静态代码块
该方式在成员位置声明 Singleton 类型的静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;static {singleton=new Singleton();}//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton() {return singleton;}
}
该方式和饿汉式的方式 1 基本一样,所以该方式也存在内存浪费问题。
懒汉式 1:线程不安全
该方式在成员位置声明 Singleton 类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?
当调用 getInstance()
方法获取 Singleton 类的对象的时候才创建 Singleton 类的对象,这样就实现了懒加载效果。
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){if(singleton==null){singleton=new Singleton();}return singleton;}
}
测试
public class Client {public static void main(String[] args) {Thread thread1=new Thread(()->{System.out.println(Singleton.getSingleton());});thread1.start();System.out.println(Singleton.getSingleton());}
}
//com.lsc.itheima.pattern.singleton.demo3.Singleton@3d075dc0
//com.lsc.itheima.pattern.singleton.demo3.Singleton@6b047ec
- 发现不是同一个对象,没有实现其单例的效果
- 为什么不是线程安全的
- 比如我们的thread1线程调用了getSingleton,进行if(singleton==null)判断,进入了判断体
- 然后我们的main线程抢占了CPU,进行运行,进行if(singleton==null)判断,因为thread1线程并没有进行创建对象,也进入了判断体
- 故两个线程各自创建了对应的对象
懒汉式 2:线程安全—方法级上锁
在懒汉式 1 的基础上,使用 synchronized
关键字对getSingleton这个方法进行加锁
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static synchronized Singleton getSingleton(){if(singleton==null){singleton=new Singleton();}return singleton;}
}
- 但是该方法的执行效率特别低。
- 懒汉模式中加锁的问题,对于 getSingleton 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式
- 注意:其实只有在初始化singleton的时候才会出现线程安全问题,一旦初始化完成就不存在了。
懒汉式 3:双重检查锁🚀
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){//第一次判断,如果singleton不为null,不进入抢锁阶段,直接返回实例if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
- 要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。
- 使用volatile关键字保证单例对象初始化不会被中断,保证其他线程获得的对象一定是初始化完成的对象
- 比如现在t1线程执行到了初始化的实例对象,正在执行new操作,还没完全结束(但是LazySingleTon!=null),然后t2线程执行到了判断唯一的对象是否为空,对于t2发现不为空,就直接返回了,但是返回的对象是没有完全初始化的,所以需要加volatile,就相当加了一层内存屏障,保证其他线程返回的对象必须等操作完全结束才能执行return语句
public class Singleton {//1定义静态成员变量,在类加载时创建// 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
懒汉式 4:静态内部类🚀
静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。
public class Singleton {//2私有化构造方法private Singleton(){}// 定义一个静态内部类private static class SingletonHolder {// 在内部类中声明并初始化外部类的对象private static final Singleton singleton = new Singleton();}// 提供公共的访问方式public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
第一次加载 Singleton 类时不会去初始化 singleton,只有第一次调用 getInstance(),虚拟机加载 SingletonHolder 类并初始化 singleton,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
饿汉式 3:枚举🚀
首先,枚举方式是饿汉式单例模式,如果不考虑浪费内存空间的问题,这是极力推荐的单例实现模式。
因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式。
枚举的写法非常简单,而且枚举方式是所用单例实现中唯一一种不会被破坏的单例实现模式。
public class SingleTon {/*** 饿汉式:枚举实现*/public enum Singleton {INSTANCE}
}
存在的问题
有两种方式可以使上面定义的单例类可以创建多个对象(枚举方式除外),分别是序列化和反射。
枚举方式是利用了 Java 特性实现的单例模式,不会被破坏,其他实现方式都有可能会被破坏
序列化破坏单例
问题演示:下面代码运行结果是false
,表明序列化和反序列化破坏了单例设计模式。
public class Client {public static void main(String[] args) throws IOException, ClassNotFoundException {writeObject2File();Singleton s1 = readObjectFormFile();Singleton s2 = readObjectFormFile();
// System.out.println(s1 == s2); // false}//从文件读取数据public static Singleton readObjectFormFile() throws IOException, ClassNotFoundException {//创建对象输入流对象ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\a.txt"));//2.读取对象Singleton singleton =(Singleton) objectInputStream.readObject();//3释放资源objectInputStream.close();return singleton;}//向文件写数据public static void writeObject2File() throws IOException {//1获取Singleton对象Singleton singleton = Singleton.getSingleton();//创建对象输出流对象ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));// 3.写对象objectOutputStream.writeObject(singleton);// 4.释放资源objectOutputStream.close();}
}
解决方案:在 Singleton 类中添加readResolve()
方法。
在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新 new 出来的对象。
/*** 双重加锁方式(解决序列化破解单例模式)*/
public class Singleton implements Serializable {//1定义静态成员变量,在类加载时创建 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}/*** 下面是为了解决序列化反序列化破解单例模式* 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回*/private Object readResolve() {return getSingleton();}}
反射破坏单例
public class Client {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {//1 获取Singleton的字节码对象Class<Singleton> singletonClass = Singleton.class;//2获取无参构造方法对象Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();//3取消访问检查declaredConstructor.setAccessible(true);// 4.创建Singleton对象Singleton s1 = (Singleton) declaredConstructor.newInstance();Singleton s2 = (Singleton) declaredConstructor.newInstance();System.out.println(s1 == s2); // false}
}
解决方案:当通过反射方式调用构造方法进行创建时,直接抛异常
public class Singleton {//1定义静态成员变量,在类加载时创建 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;private static boolean flag = false;//2私有化构造方法// 私有构造方法private Singleton() {synchronized (Singleton.class) {// 如果是true,说明非第一次访问,直接抛一个异常,如果是false,说明第一次访问if (flag) {throw new RuntimeException("不能创建多个对象");}flag = true;}}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
JDK 源码 - Runtime 类
Runtime 类使用的就是单例设计模式。
源码查看:下面是 Runtime 类的源码,是静态变量方式的饿汉单例模式。
public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class <code>Runtime</code> are instance* methods and must be invoked with respect to the current runtime object.** @return the <code>Runtime</code> object associated with the current* Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}...
}
/*** RuntimeDemo*/
public class RuntimeDemo {public static void main(String[] args) throws IOException {// 获取Runtime类的对象Runtime runtime = Runtime.getRuntime();// 调用runtime的方法exec,参数要的是一个命令Process process = runtime.exec("ifconfig");// 调用process对象的获取输入流的方法InputStream is = process.getInputStream();byte[] arr = new byte[1024 * 1024 * 100];// 读取数据int len = is.read(arr); // 返回读到的字节的个数// 将字节数组转换为字符串输出到控制台System.out.println(new String(arr, 0, len, "GBK"));}
}
相关文章:

第二章创建模式—单例设计模式
文章目录 单例模式的结构如何控制只有一个对象呢怎么设计这个类的内部对象外部怎么访问 单例模式的主要有以下角色 单例模式的实现饿汉式 1:静态变量饿汉式 2:静态代码块懒汉式 1:线程不安全懒汉式 2:线程安全—方法级上锁懒汉式 …...
数据结构学习记录——堆的插入(堆的结构类型定义、最大堆的创建、堆的插入:堆的插入的三种情况、哨兵元素)
目录 堆的结构类型定义 最大堆的创建 堆的插入 堆的插入的三种情况 代码实现 哨兵元素 堆的结构类型定义 #define ElementType int typedef struct HNode* Heap; /* 堆的类型定义 */ struct HNode {ElementType* Data; /* 存储元素的数组 */int Size; /* 堆中…...

netperf测试
netperf测试 目录 批量网络流量性能测试 TCP_STREAM测试UDP_STREAM 测试请求/应答网络流量测试 TCP_RR TCP_CRR Netperf 是一个网络性能测试工具,它可以测试网络协议栈的性能,例如TCP和UDP协议。Netperf可以测量网络吞吐量、延迟和CPU利用率等指标。…...

ORACLE常用语句
1.修改用户密码 alter user 用户名 identified by 新密码; 2.表空间扩容 1.增加数据文件 alter tablespace AA add datafile ‘DATA’ size 20G autoextend off; 2.修改数据文件大小 ALTER DATABASE DATAFILE ‘E:\ORACLE\PRODUCT\10.2.0\ORADATA\aa\aa.DBF’ RESIZE 400M;…...

[论文笔记]C^3F,MCNN:图片人群计数模型
(万能代码)CommissarMa/Crowd_counting_from_scratch 代码:https://github.com/CommissarMa/Crowd_counting_from_scratch (万能代码)C^3 Framework开源人群计数框架 科普中文博文:https://zhuanlan.zhihu.com/p/65650998 框架网址:https…...

HCIP-7.2VLAN间通信单臂、多臂、三层交换方式学习
VLAN间通信单臂、多臂、三层交换方式学习 1、单臂路由2、多臂路由3、三层交换机的SVI接口实现VLAN间通讯3.1、VLANIF虚拟接口3.2、VLAN间路由3.2.1、单台三层路由VLAN间通信,在一台三层交换机内部VLAN之间直连。3.2.2、两台三层交换机的之间的VLAN通信。3.2.3、将物…...

PHP快速入门17-用spl_autoload_register实现类的自动加载
文章目录 前言实现过程创建两个类创建入口文件 总结 前言 本文已收录于PHP全栈系列专栏:PHP快速入门与实战 PHP类自动载入是指在PHP应用程序中,当需要使用某个类文件时,系统会自动加载该类文件,无需手动引入。 在PHP中…...

【黑马程序员 C++教程从0到1入门编程】【笔记8】 泛型编程——模板
https://www.bilibili.com/video/BV1et411b73Z?p167 C泛型编程是一种编程范式,它的核心思想是编写通用的代码,使得代码可以适用于多种不同的数据类型。 而模板是C中实现泛型编程的一种机制,它允许我们编写通用的代码模板,然后在需…...

分享10个精美可视化模板,解决95%的大屏需求!
前段时间和朋友一起喝茶,我吐槽着excel表格做报表的繁琐,他惊讶的问我竟然不知道大屏模板这种东西,说是直接套用数据就可以,我震惊的同时吃下了这个安利。 回来之后,我好好研究了一番这个叫可视化大屏的“新鲜玩意儿”…...

好用的项目管理软件的具体功能有哪些
随着企业规模不断的扩大,项目管理往往会面临更多的挑战与难题,最常见的会出现以下几个问题:资源消耗失控,而项目部门和相关部门之间沟通越来越困难;团队凝聚力下降、项目进度难以把控,项目成本几乎失控&…...

< 每日小技巧: 基于Vue状态的过渡动画 - Transition 和 TransitionGroup>
》基于Vue状态的过渡动画 - Transition 和 TransitionGroup 👉 一、Vue Transition 简介> Transition 和 TransitionGroup 之间的区别 👉 二、<Transition> 组件> 触发 <Transition> 组件的场景:> 基于 CSS 的过渡效果&…...

vmware安装redhat 8
vmware安装redhat 8 1、下载镜像文件1.1 镜像文件 2、安装系统2.1、选择自定义安装2.2、兼容性选择2.3、选择镜像文件导入2.4、设置用户名密码2.5、选择虚拟机在磁盘上的位置2.6、选择处理器数量2.7、选择内存大小2.8、选择桥接或NAT2.9、选择SCSI控制器类型2.10、选择虚拟机磁…...

OpenCV C++案例实战三十一《动态时钟》
OpenCV C案例实战三十一《动态时钟》 前言一、绘制表盘二、绘制刻线三、获取系统时间四、结果展示五、源码总结 前言 本案例将使用OpenCV C实现动态时钟效果。原理也很简单,主要分为绘制表盘、以及获取系统时间两步。 一、绘制表盘 首先为了效果显示美观一点&…...

字节后端入门 - Go 语言原理与实践
1.1什么是Go语言 1.2Go语言入门 环境 1.3基础语法 1.3.1变量 var name"value" 自己推断变量类型; 也可以显式类型 var c int 1 name: type(value) 常量: const name "value" g : a"foo" 字符串拼接 1.3.2 if else {}花括号…...

锂电材料浆料匀浆搅拌设备轴承经常故障如何处理?
锂电材料浆料匀浆搅拌设备是锂电池生产中重要的设备之一,用于将活性材料、导电剂、粘结剂和溶剂混合成均匀的浆料,是电极制备过程中不可或缺的步骤。然而,由于高速搅拌和化学腐蚀等因素的影响,轴承经常会出现故障,导致…...

设计模式——设计模式介绍和单例设计模式
导航: 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线设计模式牛客面试题 目录 一、设计模式概述和分类 1.1 设计模式介绍 1.2 设计模式分类 二、创建型设计模式-单例模式 2.1 介绍 2.2 八种单例模式的创…...

利用Iptables构建虚拟路由器
利用Iptables构建虚拟路由器 (1)修改网络类型 在VMware Workstation软件中选择“编辑→虚拟网络编辑器”菜单命令,在虚拟网络列表中选中VMnet1,将其配置为“仅主机模式(在专用网络内连接虚拟机)”&#x…...

C++——类和对象[中]
0.关注博主有更多知识 C知识合集 目录 1.类的默认成员函数 2.构造函数和析构函数基础 3.构造函数进阶 4.析构函数进阶 5.拷贝构造函数 6.运算符重载 7.日期类 7.1输入&输出&友元函数 8.赋值运算符重载 9.const成员函数 9.1日期类完整代码 10.取地址重载 …...

Symbol.iterator和Symbol.asyncIterator
Symbol是什么? symbol是ES6标准中新增的一种基本数据类型,symbol 的值是通过 Symbol()函数返回的,每一个 symbol 的值都是唯一的,即使传入相同的描述值。 注:Symbol 函数不允许通过 new 的方式调用 Symbol的作用是什…...

忆暖行动|“他一个人推着老式自行车在厚雪堆的道路上走,车上带着学生考试要用的司机”
忆暖行动|“他一个人推着老式自行车在厚雪堆的道路上走,车上带着学生考试要用的sj” 一头白发,满山青葱 在那斑驳的物件褶皱中,透过泛黄的相片,掩藏着岁月的冲刷和青葱的时光。曾经的青年早已经不复年轻,但是那份热爱…...

Python中True、False、None的判断(避坑)
2.4 Python中True、False、None的判断 在Python中,所有的空值和0在作为条件表达式时,隐式的进行bool转换后都是False,比如:空列表:[]、空字符串:‘’、空字典:{}等等。 from icecream import …...

Spring Bean定义有哪些方式?
概述 对于学习Spring的兄弟姐妹来说,觉得这个问题很熟悉,若是要把它回答得很清楚,却是很为难?平时写代码的时候,不会在意这些概念问题,但面试时这个问题出现的频率却是很高,所以还是必须要掌握…...

JVM内存模型的演变
1,背景 class文件、类的加载过程。我们的class文件就要进入到JVM内存里,我们沿着经典的JDK1.6,JDK1.7,JDK1.8看看在其中都经历了哪些改变 概念的统一: 方法区: 方法区可以看作是JVM逻辑上管理一片区域的…...

DataX3同步Mysql数据库数据到Mysql数据库和DataX3同步mysql数据库数据到Starrocks数据库
DataX3同步Mysql数据库数据到Mysql数据库和DataX3同步mysql数据库数据到Starrocks 一、认识DataX二、DataX3概览三、DataX3框架设计四、DataX3插件体系五、DataX3核心架构六、DataX 3六大核心优势1.可靠的数据质量监控2.丰富的数据转换功能3.精准的速度控制4.强劲的同步性能5.健…...

你是否曾经为自己写的代码而感到懊恼?那如何才能写出高质量代码呢?
这里写目录标题 一、 前言二、高质量代码的特征三、编程实践技巧1. 遵循编码规范2. 使用有意义的变量名和函数名3. 减少代码重复4. 使用注释5. 编写单元测试6. 使用设计模式7. 使用版本控制工具8. 保持代码简洁9. 优化代码性能10. 学习和借鉴他人的代码总结 一、 前言 写出高质…...

常用 Composition API【VUE3】
二、常用 Composition API 7. 计算属性与监视 7.1 computed函数 与Vue2.x中computed配置功能一致写法 <template><h1>一个人的信息</h1>姓:<input type"text" v-model"person.firstName"><br><br>名&a…...

--商业模式--
O2O O2O,网络用语中指Online To Offline的缩写,即在线离线/线上到线下,是指将线下的商务机会与互联网结合,让互联网成为线下交易的平台。 O2O概念最早来源于美国。O2O的概念非常广泛,既可涉及到线上,又可…...

JavaWeb《HTML基础标签》
本笔记学习于Acwing平台 MDN官方文档https://developer.mozilla.org/zh-CN/ 目录 1. html文件结构 2. 文本标签 3. 图片 4. 音频和视频 5. 超链接 6. 表单 7. 列表 8. 表格 9. 语义标签 10. 特殊符号 1. html文件结构 文档结构 html的所有标签为树形结构ÿ…...

ChatGpt 能取代人类吗?
目录 前言 一、ChatGpt是什么? 二、ChatGpt能做什么 总结 前言 随着人工智能的不断发展,很多人都开启了学习机器学习,以及现在ChatGpt的出现,对人类社会带来了很多变化。 智能化交流方式:ChatGpt的出现为人们提供了…...

PHP内存溢出Allowed memory size of 解决办法
以前追踪过这个问题,但是那个时候工具用的不太好,没看的这么细,这次搞的比较细,修正了偶以前的看法 .于是写小文一篇总结一下. PHP偶尔会爆一下如下 错误Allowed memory size of xxx bytes exhausted at xxx:xxx (tried to allocate xxx bytes) 不想看原理的,直接跳到最后…...