初级必备:单例模式的7个问题

故事

实话实说,关于单例模式,网上有N多个版本。你估计也看过很多版本。但看完了又能怎样?我技术群里的一位小伙伴,上周面试,就因为一个单例模式,然后叫他回去等通知了。

下面是这位同学被问到的问题:

1、说说单例模式的特点?

2、你知道单例模式的具体使用场景吗?

3、单例模式常见写法有几种?

4、怎么样保证线程安全?

5、怎么不会被反射攻击?

6、怎样保证不会被序列化和反序列化的攻击?

7、枚举为什么会不会被序列化?

.....

你也可以尝试行的回答这几个题,看看自己能回答上几个。

定义

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

特点:

  • 1、单例类只能有一个实例。

  • 2、单例类必须自己创建自己的唯一实例。

  • 3、单例类必须给所有其他对象提供这一实例

  • 4、隐藏所有的构造方法

**目的:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。

案例:一家企业只能有一个CEO,有多个了其实乱套了。

使用场景

需要确保任何情况下都绝对只有一个实例。

比如:ServletContextServletConfigApplicationContextDBTool等,都使用到了单列模式。

单例模式的写法

  • 饿汉式

  • 懒汉式(包含双重检查锁、静态内部类)

  • 注册式(以枚举为例)

饿汉式

从名字上就能看出,饿汉:饿了就得先吃饱,所以,一开始就搞定了。

饿汉式主要是使用了static,饿汉式也有两种写法,但本质可以理解为是一样的。

public class HungrySingleton{private static final HungrySingleton INSTANCE;static {INSTANCE=new HungrySingleton();}
//    private static final HungrySingleton INSTANCE=new HungrySingleton();private HungrySingleton(){}public static HungrySingleton getInstance(){return INSTANCE;}
}

饿汉式有个致命的缺点:浪费空间,不需要也实例化。如果是成千上万个,也这么玩,想想有多恐怖。

于是,就会想到,能不能在使用的时候在实例化,从而引出了懒汉式。

懒汉式

顾名思义,就是需要的时候再创建,因为懒,你不调用我方法,我是不会干活的。

下面是懒汉式的Java代码实现:

public class LazySingleton {private static LazySingleton lazySingleton = null;private LazySingleton() {}public static LazySingleton getInstance() {if (lazySingleton == null) {//01lazySingleton = new LazySingleton();//02}return lazySingleton;} 
}

进入getInstance方法,先判断lazySingleton是否为空,为空,则创建一个对象,然后返回此对象。

但是,问题来了:

两个线程同时进入getInstance方法,然后都去执行01这行代码,都是true,然后各自进去创建一个对象,然后返回自己创建的对象。

这岂不是不满足只有唯一 一个对象的了吗?所以这类存在线程安全的问题,那怎么解决呢?

第一印象肯定都是想到加锁。于是,就有了下面的线程安全的懒加载版本:

public class LazySingleton {private static LazySingleton lazySingleton = null;private LazySingleton() {}//简单粗暴的线程安全问题解决方案//依然存在性能问题public synchronized static LazySingleton getInstance() {if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}
}

给getInstance方法加锁同步锁标志synchronized,但是又涉及到锁的问题了,同步锁是对系统性能优影响的,尽管JDK1.6后,对其做了优化,但它毕竟还是涉及到锁的开销。

每个线程调用getInstance方法时候,都会涉及到锁,所以又对此进行了优化成为了大家耳熟能详的双重检查锁。

双重检查锁

代码实现如下:

public class LazyDoubleCheckSingleton { private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;private LazyDoubleCheckSingleton() {}public static LazyDoubleCheckSingleton getInstance() {if (lazyDoubleCheckSingleton == null) {//01synchronized (LazyDoubleCheckSingleton.class) {if (lazyDoubleCheckSingleton == null) {//02lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();}}}return lazyDoubleCheckSingleton;}}

这段代码中,在01行,如果不为空,就直接返回,这是第一次检查。如果为空,则进入同步代码块,02行又进行一次检查。

双重检查就是现实if判断、获取类对象锁、if判断。

上面这段代码,看似没问题,其实还是有问题的,比如:指令重排序(需要有JVM知识垫底哈)

指令重排是什么意思呢?

比如java中简单的一句

lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();

会被编译器编译成如下JVM指令:

memory =allocate();    //1:分配对象的内存空间

ctorInstance(memory);  //2:初始化对象

instance =memory;     //3:设置instance指向刚分配的内存地址

但是这些指令顺序并非一成不变,有可能会经过JVM和CPU的优化,指令重排成下面的顺序:

memory =allocate();    //1:分配对象的内存空间

instance =memory;     //3:设置instance指向刚分配的内存地址

ctorInstance(memory);  //2:初始化对象

为了防止指令重排序,所以,我们可以使用volatile来做文章(注意:volatile能防止指令重排序和线程可见性)。

于是,更好的版本就出来了。

public class LazyDoubleCheckSingleton {//使用volatile修饰private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; private LazyDoubleCheckSingleton() {}public static LazyDoubleCheckSingleton getInstance() {if (lazyDoubleCheckSingleton == null) {synchronized (LazyDoubleCheckSingleton.class) {if (lazyDoubleCheckSingleton == null) {lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();}}}return lazyDoubleCheckSingleton;}
}

尽管相比前面的版本,确实改进了很多,但依然有同步锁,还是会影响性能问题。于是,又进行优化为静态内部类方式:

静态内部类

下面是静态内部类的代码实现:

public class LazyStaticSingleton {private LazyStaticSingleton() {}public static LazyStaticSingleton getInstance() {return LazyHolder.LAZY_STATIC_SINGLETON;}//需要等到外部方法调用是猜执行//巧用内部类的特性//JVM底层执行,完美的规避了线程安全的问题private static class LazyHolder {private static final LazyStaticSingleton LAZY_STATIC_SINGLETON = new LazyStaticSingleton();}
}

利用了内部类的特性,在JVM底层,能完美的规避了线程安全的问题,这种方式也是目前很多项目里喜欢使用的方式。

但是,还是会存在潜在的风险,什么风险呢?

可以使用 反射 暴力的串改,同样也会出现创建多个实例:

反射代码实现如下:

import java.lang.reflect.Constructor;public class LazyStaticSingletonTest {public static void main(String[] args) {try {Class<?> clazz = LazyStaticSingleton.class;Constructor constructor = clazz.getDeclaredConstructor(null);//强行访问constructor.setAccessible(true);Object object = constructor.newInstance();Object object1 = LazyStaticSingleton.getInstance();System.out.println(object == object1);} catch (Exception ex) {ex.printStackTrace();}}
}

这段代码运行结果为false。

所以,上面说的双重检查锁的方式,通过反射,还是会存在潜在的风险。怎么办呢?

在《Effect java 》这本书中,作者推荐使用枚举来实现单例模式,因为枚举不能被反射。

枚举

下面是枚举式的单例模式的代码实现:

public enum EnumSingleton {INSTANCE;private Object data;public Object getData() {return data;}public static EnumSingleton getInstance(){return INSTANCE;}
}

我们把上面反射的那个代码,来测试这个枚举式单例模式。

public class EnumTest {public static void main(String[] args) {try {Class<?> clazz = EnumSingleton.class;Constructor constructor = clazz.getDeclaredConstructor(null);//强行访问constructor.setAccessible(true);Object object = constructor.newInstance();Object object1 = EnumSingleton.getInstance();System.out.println(object == object1);} catch (Exception ex) {ex.printStackTrace();}}
}

运行这段代码:

java.lang.NoSuchMethodException: com.tian.my_code.test.designpattern.singleton.EnumSingleton.<init>()at java.lang.Class.getConstructor0(Class.java:3082)at java.lang.Class.getDeclaredConstructor(Class.java:2178)at com.tian.my_code.test.designpattern.singleton.EnumTest.main(EnumTest.java:41)

还真的不能用反射来搞。如果此时面试官,为什么枚举不能被反射呢

为什么枚举不能被反射呢?

我们在反射的代码中

  Constructor constructor = clazz.getDeclaredConstructor(null);

这行代码是获取他的无参构造方法。并且,从错误日志中,我们也可以看到,错误出现就是在getConstructor0方法中,并且,提示的是没有找到无参构造方法。

很奇怪,枚举也是类,不是说如果我们不给类显示定义构造方法时候,会默认给我们创建一个无参构造方法吗?

于是,我想到了一个办法,我们可以使用jad这个工具去反编译的我们的枚举式单例的.class文件。

找到我们的class文件所在目录,然后我们可以执行下面这个命令:

C:\Users\Administrator>jad D:\workspace\my_code\other-local-demo\target\classes
com\tian\my_code\test\designpattern\singleton\EnumSingleton.class
Parsing D:\workspace\my_code\other-local-demo\target\classes\com\tian\my_code\t
st\designpattern\singleton\EnumSingleton.class... Generating EnumSingleton.jad

注意:class文件目录以及生成的jad文件所在的目录。

然后打开EnumSingleton.jad 文件:

 

于是,我就想到了,那我们使用有参构造方法来创建:

public class EnumTest {public static void main(String[] args) {try {Class<?> clazz = EnumSingleton.class; Constructor constructor = clazz.getDeclaredConstructor(String.class,int.class);//强行访问constructor.setAccessible(true);Object object = constructor.newInstance("田维常",996);Object object1 = EnumSingleton.getInstance();System.out.println(object == object1);} catch (Exception ex) {ex.printStackTrace();}}
}

再次运行这段代码,结果:

java.lang.IllegalArgumentException: Cannot reflectively create enum objectsat java.lang.reflect.Constructor.newInstance(Constructor.java:417)at com.tian.my_code.test.designpattern.singleton.EnumTest.main(EnumTest.java:45)

提示很明显了,就是不让我们使用反射的方式创建枚举对象。

    public T newInstance(Object ... initargs)throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, null, modifiers);}}//Modifier.ENUM就是用来判断是否为枚举的if ((clazz.getModifiers() & Modifier.ENUM) != 0)throw new IllegalArgumentException("Cannot reflectively create enum objects");ConstructorAccessor ca = constructorAccessor;   // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@SuppressWarnings("unchecked")T inst = (T) ca.newInstance(initargs);return inst;}

所以,到此,我们才算真正的理清楚了,为什么枚举不让反射的原因。

序列化破坏

我们以非线程安全的饿汉式来演示一下,看看序列化是如何破坏到了模式的。

public class ReflectTest {public static void main(String[] args) {// 准备两个对象,singleton1接收从输入流中反序列化的实例HungrySingleton singleton1 = null;HungrySingleton singleton2 = HungrySingleton.getInstance();try {// 序列化FileOutputStream fos = new FileOutputStream("HungrySingleton.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(singleton2);oos.flush();oos.close();// 反序列化FileInputStream fis = new FileInputStream("HungrySingleton.txt");ObjectInputStream ois = new ObjectInputStream(fis);singleton1 = (HungrySingleton) ois.readObject();ois.close();System.out.println(singleton1);System.out.println(singleton2);System.out.println(singleton1 == singleton2);} catch (Exception e) {e.printStackTrace();}}
}

运行结果:

com.tian.my_code.test.designpattern.singleton.HungrySingleton@7e6cbb7a
com.tian.my_code.test.designpattern.singleton.HungrySingleton@452b3a41
false

看到了吗?

使用序列化是可以破坏到了模式的,这种方式,可能很多人不是很清楚。

如何防止呢?

我们对非线程安全的饿汉式代码进行稍微修改:

public class HungrySingleton implements Serializable{private static final HungrySingleton INSTANCE;static {INSTANCE=new HungrySingleton();} private HungrySingleton(){}public static HungrySingleton getInstance(){return INSTANCE;}//添加了readResolve方法,并返回INSTANCEprivate Object readResolve方法,并返回(){return INSTANCE;}
}

再次运行上那段序列化测试的代码,其结果如下:

com.tian.my_code.test.designpattern.singleton.HungrySingleton@452b3a41
com.tian.my_code.test.designpattern.singleton.HungrySingleton@452b3a41
true

嘿嘿,这样我们是不是就避免了只创建了一个实例?

答案:否

在类ObjectInputStream的readObject()方法中调用了另外一个方法readObject0(false)方法。在readObject0(false)方法中调用了checkResolve(readOrdinaryObject(unshared))方法。

在readOrdinaryObject方法中有这么一段代码:

Object obj;
try { //是否有构造方法,有构造放就创建实例obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {... }
//判断单例类是否有readResolve方法
if (desc.hasReadResolveMethod()) {Object rep = desc.invokeReadResolve(obj); 
}//invokeReadResolve方法中
if (readResolveMethod != null) { //调用了我们单例类中的readResolve,并返回该方法返回的对象//注意:是无参方法return readResolveMethod.invoke(obj, (Object[]) null);
}

绕了半天,原来他是这么玩的,上来就先创建一个实例,然后再去检查我们的单例类是否有readResolve无参方法,我们单例类中的readResolve方法

private Object readResolve(){return INSTANCE;
}

结论

我们重写了readResolve()无参方法,表面上看是只创建了一个实例,其实只创建了两个实例。

紧接着,面试官继续问:枚举式单例能不能被序列化破坏呢?

枚举式单例能不能被序列化破坏呢?

答案:不能被破坏,请看我慢慢给你道来。

don't talk ,show  me the code。

我们先来验证一下是否真的不能被破坏,请看代码:

public class EnumTest {public static void main(String[] args) {// 准备两个对象,singleton1接收从输入流中反序列化的实例EnumSingleton singleton1 = null;EnumSingleton singleton2 = EnumSingleton.getInstance();try {// 序列化FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(singleton2);oos.flush();oos.close();// 反序列化FileInputStream fis = new FileInputStream("EnumSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);singleton1 = (EnumSingleton) ois.readObject();ois.close();System.out.println(singleton1);System.out.println(singleton2);System.out.println(singleton1 == singleton2);} catch (Exception e) {e.printStackTrace();}}
}

运行结果:

INSTANCE
INSTANCE
true

确实,枚举式单例是不会被序列化所破坏,那为什么呢?总得有个证件理由吧。

在类ObjectInputStream的readObject()方法中调用了另外一个方法readObject0(false)方法。在readObject0(false)方法中调用了checkResolve(readOrdinaryObject(unshared))方法。

 case TC_ENUM:return checkResolve(readEnum(unshared));

在readEnum方法中

private Enum<?> readEnum(boolean unshared) throws IOException {if (bin.readByte() != TC_ENUM) {throw new InternalError();}Class<?> cl = desc.forClass();if (cl != null) {try {@SuppressWarnings("unchecked")//重点Enum<?> en = Enum.valueOf((Class)cl, name);result = en;//...其他代码省略}}
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {//enumType.enumConstantDirectory()返回的是一个HashMap//通过HashMap的get方法获取T result = enumType.enumConstantDirectory().get(name);if (result != null)return result;if (name == null)throw new NullPointerException("Name is null");throw new IllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." + name);
}
//返回一个HashMapMap<String, T> enumConstantDirectory() {if (enumConstantDirectory == null) {T[] universe = getEnumConstantsShared();if (universe == null)throw new IllegalArgumentException(getName() + " is not an enum type");//使用的是HashMapMap<String, T> m = new HashMap<>(2 * universe.length);for (T constant : universe)m.put(((Enum<?>)constant).name(), constant);enumConstantDirectory = m;}return enumConstantDirectory;
}

所以,枚举式单例模式是使用了Map<String, T>,Map的key就是我们枚举类中的INSTANCE。由于Map的key的唯一性,然后就缔造出唯一实例。江湖上也把这个枚举式单例模式叫做注册式单例模式

在Spring中也是有大量使用这种注册式单例模式,IOC容器就是典型的代表。

总结

本文讲述了单例模式的定义、单例模式常规写法。单例模式线程安全问题的解决,反射破坏、反序列化破坏等。

注意:不要为了套用设计模式,而使用设计模式。而是要,在业务上遇到问题时,很自然地联想单设计模式作为一种捷径方法。

单例模式的优缺点

优点

在内存中只有一个实例,减少内存开销。可以避免对资源的多重占用。设置全局访问点,严格控制访问。

缺点

没有借口,扩展性很差。如果要扩展单例对象,只有修改代码,没有其他途径。

单例模式是 不符合开闭原则的。

知识点

单例模式的重点知识总结:

  • 私有化构造器

  • 保证线程安全

  • 延迟加载

  • 防止反射攻击

  • 防止序列化和反序列化的破坏

     

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283529.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring AOP源码解析——AOP动态代理原理和实现方式

2019独角兽企业重金招聘Python工程师标准>>> Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架&#xff0c;同时也是轻量级的IoC和AOP的容器框架&#xff0c;主要是针对JavaBean的生命周期进行管理的轻量级容器&#xff0c;可以单独使用&#xff0…

使用.NET构建简单的高性能Redis(三)

译者注该原文是Ayende Rahien大佬业余自己在使用C# 和 .NET构建一个简单、高性能兼容Redis协议的数据库的经历。首先这个"Redis"是非常简单的实现&#xff0c;但是他在优化这个简单"Redis"路程很有趣&#xff0c;也能给我们在从事性能优化工作时带来一些启…

海尔联手软银机器人,进军服务机器人领域

海尔此次将正式全面进入到服务机器人。 据悉&#xff0c;3月6日海尔公布了未来对于家用机器人的最新战略&#xff0c;同时与软银展开战略合作&#xff0c;将软银的人形机器人引入中国市场&#xff0c;正式进军服务机器人领域。 在本次发布会上&#xff0c;海尔与软银将联手从软…

.NET 7 中的 EndpointFilter

ASP.NET 7 中的 EndpointFilterIntro.NET 6 开始微软引入了 Minimal API&#xff0c;但是相比成熟的控制器模型&#xff0c;还是有很多的不足&#xff0c;.NET 7 针对于 Minimal API 也做了一些改进来让 Minimal API 功能更加丰富&#xff0c;其中 Filter 就是其中的一个更新从…

越来越火的图数据库到底能做什么?

作者 | 俞方桦 随着大数据时代的到来&#xff0c;传统的关系型数据库由于其在数据建模和存储方面的限制&#xff0c;变得越来越难以满足大量频繁变化的需求。关系型数据库&#xff0c;尽管其名称中有“关系”这个词&#xff0c;却并不擅长处理复杂关系的查询和分析。另外&…

piwik抓取用户交互行为

2019独角兽企业重金招聘Python工程师标准>>> https://github.com/matomo-org/matomo-sdk-ios/tree/version-3 http://piwik.org 首先下载demo&#xff0c;把文件拖到项目中去&#xff0c;在AppDelegate.m文件填写piwik服务器的URL和编号&#xff1b; 例如&#xff1…

k8s 读书笔记 - kubernetes 基本概念和术语(下)

DevOps前言上一篇文章 中&#xff0c;我们介绍了 k8s 中的 Master、Node、Pod、Label、RC & RS、Deployment、HPA & VPA、DaemonSet 这些资源对象信息&#xff0c;接下来我们继续介绍 k8s 中常用的资源对象。StatefulSet在 k8s 系统中&#xff0c;Pod 的管理对象 RC、D…

java数据类型后加三个点...

2019独角兽企业重金招聘Python工程师标准>>> 从Java5开始&#xff0c;Java语言对方法参数支持一种新写法&#xff0c;varargs&#xff08;可变长度参数列表&#xff09;&#xff0c;其语法就是类型后跟...&#xff0c;表示此处接受的参数为0到多个Object类型的对象&…

手把手教你用 Jenkins 自动部署 SpringBoot!

1. 什么是 CI/CD 1.1 CI&#xff08;Continuous Integration&#xff09; 1.2 CD&#xff08;Continuous Delivery/Continuous Deployment&#xff09; 2. 什么是 Jenkins 3. 准备工作 3.1 整体规划 3.2 准备代码 3.3 准备服务器 4. 搭建 Jenkins 5. 安装插件 6. 配置 …

bondat蠕虫传播与对抗

转载来自&#xff1a;http://www.mottoin.com/109730.html &#xff08;1&#xff09;可移动磁盘传播手段&#xff1a;隐藏U盘文件&#xff0c;创建快捷方式指向病毒bat文件。Bondat蠕虫主要通过可移动磁盘传播&#xff0c;并借助可移动磁盘中的文件隐蔽自身。Bondat蠕虫会检索…

vim 编译 Python 代码提示配置

2019独角兽企业重金招聘Python工程师标准>>> .vim 和.vimrc 拷贝到根目录 注意根目录下默认是没有.vim的&#xff0c;所以拷贝.vim 没问题&#xff0c;但是拷贝.vimrc 之前需要把原来的.vimrc备份 两个文件下载&#xff1a;http://pan.baidu.com/s/1eRRhakM 转载于:…

[转]Pinia与Vuex的对比:Pinia是Vuex的良好替代品吗?

文章目录 介绍设置 Pinia 设置Vuex 设置使用 Pinia使用Vuex使用社区和生态系统的力量学习曲线和文档GitHub 评分性能比较 Pinia 2 和 Vuex 4Vuex 和 Pinia 的优缺点何时使用Pinia&#xff0c;何时使用Vuex介绍 Pinia 是 Vue.js 的轻量级状态管理库&#xff0c;最近很受欢迎。它…

1.2开发文档简读,了解全貌.mp4

转载于:https://www.cnblogs.com/ZHONGZHENHUA/p/6910254.html

开源:一款开源的高颜值现代化桌面美化工具

背景在日常的工作或学习中&#xff0c;难免会有一些临时的文件夹&#xff0c;文件&#xff0c;应用&#xff0c;出现在你的桌面&#xff0c;但是呢你又不确定它是不是哪一天会突然用到&#xff0c;这样一天又一天&#xff0c;直至你的电脑桌面是一片狼藉&#xff0c;满屏的文件…

软件工程—团队作业1

软件工程—团队作业1 团队称号&#xff1a;Thanos &#xff08;灭霸&#xff0c;超叼的一个动漫人物&#xff09; 团队成员&#xff1a; 队长 成凯 1600802002 博客链接&#xff1a; http://www.cnblogs.com/ck03/ 党康 1600208004 博客链接&#xff1a; http://www.cnblogs…

k8s 读书笔记 - kubernetes 基本概念和术语(上)

k8s 资源控制系统k8s 中大部分概念如&#xff1a;Node、Pod、Replication Controller、RS、Deployment、Service 等都可以被看作一种资源对象&#xff0c;激活所有的资源对象都可以通过 k8s 提供 kubectl 工具&#xff08;或者 API 编程调用&#xff09;执行 CRUD 等操作并将其…

CentOs6.5下安装svn

1、检查是否已安装 rpm -qa subversion 1、1如果需要卸载旧版本&#xff08;如果想在一台机器安装不同svn&#xff0c;切记不要执行此步骤&#xff01;&#xff01;&#xff01;&#xff09; yum remove subversion 2、安装 yum install subversion 3、检查安装是否成功 svnser…

Android 升级到android studio 2.2项目死活run不起来

背景&#xff1a;升级到Android studio 2.2项目死活运行不起来 现象如下&#xff1a; run with --stacktrace --debug等等抛出的bug简直无法忍视 解决办法&#xff1a;把compileSdkVersion 改为23成功run起来了

【python】-- Django 中间件、缓存、信号

Django 中间件、缓存、信号 一、 Django 中间件 django 中的中间件&#xff08;middleware&#xff09;&#xff0c;在django中&#xff0c;中间件其实就是一个类&#xff0c;在请求到来和结束后&#xff0c;django会根据自己的规则在合适的时机执行中间件中相应的方法。 在d…

【温故知新】C#中 IEnumerable 与IQueryable

微信公众号&#xff1a;趣编程ACE关注可了解更多的.NET日常实战开发技巧&#xff0c;如需源码 后台回复 源码 即可;如果觉得对你有帮助&#xff0c;欢迎关注老生常谈 C#中 IEnumerable 与IQueryableIEnumerable 与 IQueryable 对于.Neter来说并不陌生&#xff0c;今天我就着重阐…