??xml version="1.0" encoding="utf-8" standalone="yes"?>
=TEXT((C4/1000+8*3600)/86400+70*365+19,"yyyy-mm-dd hh:mm:ss")
其中C4单元格所存的数据为ms因此需要除?000
转换后的l果如下Q?/p>
1. 前言
单例(Singleton)应该是开发者们最熟悉的设计模式了Qƈ且好像也是最Ҏ实现?#8212;—基本上每个开发者都能够随手写出——但是Q真的是q样吗?
作ؓ一个Java开发者,也许你觉得自己对单例模式的了解已l够多了。我q不惛_a耸听说一定还有你不知道的——毕竟我自q了解也的有限,但究竟你自己了解的程度到底怎样呢?往下看Q我们一h聊聊看~
2. 什么是单例Q?/p>
单例对象的类必须保证只有一个实例存?#8212;—q是l基癄上对单例的定义,q也可以作ؓҎ囑֮现单例模式的代码q行验的标准?/p>
对单例的实现可以分ؓ两大c?#8212;—懒汉式和饿汉式,他们的区别在于:
懒汉式:指全局的单例实例在W一ơ被使用时构建?/p>
饿汉式:指全局的单例实例在c装载时构徏?/p>
从它们的区别也能看出来,日常我们使用的较多的应该是懒汉式的单例,毕竟按需加蝲才能做到资源的最大化利用嘛~
3. 懒汉式单?/p>
先来看一下懒汉式单例的实现方式?/p>
3.1 单版?/p>
看最单的写法Version 1Q?/p>
// Version 1 public class Single1 { private static Single1 instance; public static Single1 getInstance() { if (instance == null) { instance = new Single1(); } return instance; } }
或者再q一步,把构造器改ؓU有的,q样能够防止被外部的c调用?/p>
// Version 1.1 public class Single1 { private static Single1 instance; private Single1() {} public static Single1 getInstance() { if (instance == null) { instance = new Single1(); } return instance; } }
我仿佛记得当初学校的教科书就是这么教的?—— 每次获取instance之前先进行判断,如果instance为空new一个出来,否则q接返回已存在的instance?/p>
q种写法在大多数的时候也是没问题的。问题在于,当多U程工作的时候,如果有多个线E同时运行到if (instance == null)Q都判断为nullQ那么两个线E就各自会创Z个实?#8212;—q样一来,׃是单例了?/p>
3.2 synchronized版本
那既然可能会因ؓ多线E导致问题,那么加上一个同步锁吧!
修改后的代码如下Q相对于Version1.1Q只是在Ҏ{上多加了一个synchronizedQ?/p>
// Version 2 public class Single2 { private static Single2 instance; private Single2() {} public static synchronized Single2 getInstance() { if (instance == null) { instance = new Single2(); } return instance; } }
OKQ加上synchronized关键字之后,getInstanceҎ׃锁上了。如果有两个U程QT1、T2Q同时执行到q个ҎӞ会有其中一个线ET1获得同步锁,得以l箋执行Q而另一个线ET2则需要等待,当第T1执行完毕getInstance之后Q完成了null判断、对象创建、获得返回g后)QT2U程才会执行执行?#8212;—所以这端代码也避免了Version1中,可能出现因ؓ多线E导致多个实例的情况?/p>
但是Q这U写法也有一个问题:lgitInstanceҎ加锁Q虽然会避免了可能会出现的多个实例问题,但是会强刉T1之外的所有线E等待,实际上会对程序的执行效率造成负面影响?/p>
3.3 双重查(Double-CheckQ版?/p>
Version2代码相对于Version1d代码的效率问题,其实是ؓ了解?%几率的问题,而用了一?00%出现的防护盾。那有一个优化的思\Q就是把100%出现的防护盾Q也改ؓ1%的几率出玎ͼ使之只出现在可能会导致多个实例出现的地方?/p>
——有没有这LҎ呢?当然是有的,改进后的代码Vsersion3如下Q?/p>
// Version 3 public class Single3 { private static Single3 instance; private Single3() {} public static Single3 getInstance() { if (instance == null) { synchronized (Single3.class) { if (instance == null) { instance = new Single3(); } } } return instance; } }
q个版本的代码看h有点复杂Q注意其中有两次if (instance == null)的判断,q个叫做『双重检?Double-Check』?/p>
W一个if (instance == null)Q其实是Z解决Version2中的效率问题Q只有instance为null的时候,才进入synchronized的代码段——大大减少了几率?/p>
W二个if (instance == null)Q则是跟Version2一P是ؓ了防止可能出现多个实例的情况?/p>
—— q段代码看v来已l完无瑕了?/p>
……
……
……
—— 当然Q只是『看h』,q是有小概率出现问题的?/p>
q弄清楚Z么这里可能出现问题,首先Q我们需要弄清楚几个概念Q原子操作、指令重排?/p>
知识点:什么是原子操作Q?/p>
单来_原子操作QatomicQ就是不可分割的操作Q在计算ZQ就是指不会因ؓU程调度被打断的操作?/p>
比如Q简单的赋值是一个原子操作:
m = 6; // q是个原子操?/span>
假如m原先的gؓ0Q那么对于这个操作,要么执行成功m变成?Q要么是没执行mq是0Q而不会出现诸如m=3q种中间?#8212;—即是在q发的线E中?/p>
而,声明q赋值就不是一个原子操作:
int n = 6; // q不是一个原子操?/span>
对于q个语句Q至有两个操作Q?/p>
①声明一个变量n
②ln赋gؓ6
——q样׃有一个中间状态:变量n已经被声明了但是q没有被赋值的状态?/p>
——q样Q在多线E中Q由于线E执行顺序的不确定性,如果两个U程都用mQ就可能会导致不E_的结果出现?/p>
知识点:什么是指o重排Q?/p>
单来_是计算Zؓ了提高执行效率,会做的一些优化,在不影响最l结果的情况下,可能会对一些语句的执行序q行调整?/p>
比如Q这一D代码:
正常来说Q对于顺序结构,执行的顺序是自上CQ也?234?/p>
但是Q由于指令重排的原因Q因Z影响最l的l果Q所以,实际执行的顺序可能会变成3124或?324?/p>
׃语句3?没有原子性的问题Q语?和语?也可能会拆分成原子操作,再重排?/p>
——也就是说Q对于非原子性的操作Q在不媄响最l结果的情况下,其拆分成的原子操作可能会被重新排列执行顺序?/p>
OKQ了解了原子操作和指令重排的概念之后Q我们再l箋看Version3代码的问题?/p>
下面q段话直接从陈皓的文?深入出单实例SINGLETON设计模式)中复制而来Q?/p>
主要在于singleton = new Singleton()q句Q这q是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情?/p>
1. l?singleton 分配内存
2. 调用 Singleton 的构造函数来初始化成员变量,形成实例
3. singleton对象指向分配的内存空_执行完这?singleton才是?null 了)
但是?JVM 的即时编译器中存在指令重排序的优化。也是说上面的W二步和W三步的序是不能保证的Q最l的执行序可能?1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕? 未执行之前,被线E二抢占了,q时 instance 已经是非 null 了(但却没有初始化)Q所以线E二会直接返?instanceQ然后用,然后理成章地报错?/p>
再稍微解释一下,是_׃有一个『instance已经不ؓnull但是仍没有完成初始化』的中间状态,而这个时候,如果有其他线E刚好运行到W一层if (instance == null)q里Q这里读取到的instance已经不ؓnull了,所以就直接把这个中间状态的instance拿去用了Q就会生问题?/p>
q里的关键在?#8212;—U程T1对instance的写操作没有完成Q线ET2执行了L作?/p>
3.4 l极版本Qvolatile
对于Version3中可能出现的问题Q当然这U概率已l非常小了,但毕竟还是有的嘛~Q,解决Ҏ是:只需要给instance的声明加上volatile关键字即可,Version4版本Q?/p>
// Version 4 public class Single4 { private static volatile Single4 instance; private Single4() {} public static Single4 getInstance() { if (instance == null) { synchronized (Single4.class) { if (instance == null) { instance = new Single4(); } } } return instance; } }
volatile关键字的一个作用是止指o重排Q把instance声明为volatile之后Q对它的写操作就会有一个内存屏障(什么是内存屏障Q)Q这P在它的赋值完成之前,׃用会调用L作?/p>
注意QvolatileL的不singleton = new Singleton()q句话内部[1-2-3]的指令重排,而是保证了在一个写操作Q[1-2-3]Q完成之前,不会调用L作(if (instance == null)Q?/p>
——也就d防止了Version3中的问题发生?/p>
——好了Q现在彻底没什么问题了吧?
……
……
……
好了Q别紧张Q的没问题了。大名鼎鼎的EventBus中,其入口方法EventBus.getDefault()是用这U方法来实现的?/p>
……
……
……
不过Q非要挑点刺的话q是能挑出来的,是q个写法有些复杂了,不够优雅、简z?/p>
4. 饿汉式单?/p>
下面再聊了解一下饿汉式的单例?/p>
如上所_饿汉式单例是指:指全局的单例实例在c装载时构徏的实现方式?/p>
׃c装载的q程是由cd载器QClassLoaderQ来执行的,q个q程也是由JVM来保证同步的Q所以这U方式先天就有一个优?#8212;—能够免疫许多由多U程引v的问题?/p>
4.1 饿汉式单例的实现方式
饿汉式单例的实现如下Q?/p>
//饿汉式实?/span> public class SingleB { private static final SingleB INSTANCE = new SingleB(); private SingleB() {} public static SingleB getInstance() { return INSTANCE; } }
对于一个饿汉式单例的写法来_它基本上是完的了?/p>
所以它的缺点也只是饿汉式单例本n的缺Ҏ在了——׃INSTANCE的初始化是在cd载时q行的,而类的加载是由ClassLoader来做的,所以开发者本来对于它初始化的时机很隑֎准确把握Q?/p>
可能׃初始化的太早Q造成资源的浪?/p>
如果初始化本w依赖于一些其他数据,那么也就很难保证其他数据会在它初始化之前准备好?/p>
当然Q如果所需的单例占用的资源很少Qƈ且也不依赖于其他数据Q那么这U实现方式也是很好的?/p>
知识点:什么时候是c装载时Q?/p>
前面提到了单例在c装载时被实例化Q那I竟什么时候才是『类装蝲时』呢Q?/p>
不严格的_大致有这么几个条件会触发一个类被加载:
1. new一个对象时
2. 使用反射创徏它的实例?/p>
3. 子类被加载时Q如果父c还没被加蝲Q就先加载父c?/p>
4. jvm启动时执行的ȝ会首先被加蝲
5. 一些其他的实现方式
5.1 Effective Java 1 —— 静态内部类
《Effective Java》一书的W一版中推荐了一个中写法Q?/p>
// Effective Java W一版推荐写?/span> public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
q种写法非常巧妙Q?/p>
对于内部cSingletonHolderQ它是一个饿汉式的单例实玎ͼ在SingletonHolder初始化的时候会由ClassLoader来保证同步,使INSTANCE是一个真·单例?/p>
同时Q由于SingletonHolder是一个内部类Q只在外部类的Singleton的getInstance()中被使用Q所以它被加载的时机也就是在getInstance()ҎW一ơ被调用的时候?/p>
——它利用了ClassLoader来保证了同步Q同时又能让开发者控制类加蝲的时机。从内部看是一个饿汉式的单例,但是从外部看来,又的是懒汉式的实现?/p>
直是乎其技?/p>
5.2 Effective Java 2 —— 枚D
你以为到q就完了?不,q没有,因ؓ厉害的大又发现了其他的Ҏ?/p>
《Effective Java》的作者在q本书的W二版又推荐了另外一U方法,来直接看代码Q?/p>
// Effective Java W二版推荐写?/span> public enum SingleInstance { INSTANCE; public void fun1() { // do something } } // 使用 SingleInstance.INSTANCE.fun1();
看到了么Q这是一个枚丄?#8230;…qclass都不用了Q极?/p>
׃创徏枚D实例的过E是U程安全的,所以这U写法也没有同步的问题?/p>
作者对q个Ҏ的评P
q种写法在功能上与共有域Ҏ相近Q但是它更简z,无偿地提供了序列化机Ӟl对防止Ҏ实例化,即是在面对复杂的序列化或者反攻ȝ时候。虽然这中方法还没有q泛采用Q但是单元素的枚丄型已l成为实现Singleton的最x法?/p>
枚D单例q种Ҏ问世一些,许多分析文章都称它是实现单例的最完美Ҏ——写法单,而且又能解决大部分的问题?/p>
不过我个U方法虽然很优秀Q但是它仍然不是完美?#8212;—比如Q在需要承的场景Q它׃适用了?/p>
6. ȝ
OKQ看到这里,你还会觉得单例模式是最单的设计模式了么Q再回头看一下你之前代码中的单例实现Q觉得是无懈可击的么Q?/p>
可能我们在实际的开发中Q对单例的实现ƈ没有那么严格的要求。比如,我如果能保证所有的getInstance都是在一个线E的话,那其实第一U最单的教科书方式就够用了。再比如Q有时候,我的单例变成了多例也可能对程序没什么太大媄?#8230;…
但是Q如果我们能了解更多其中的细节,那么如果哪天E序Z些问题,我们L能多一个排查问题的炏V早点解决问题,p早点回家吃饭……
—— q有Q完的Ҏ是不存在QQ何方式都会有一个『度』的问题。比如,你的觉得代码已经无懈可击了,但是因ؓ你用的是JAVA语言Q可能ClassLoader有些BUG?#8230;…你的代码谁运行在JVM上的Q可能JVM本n有BUG?#8230;…你的代码q行在手ZQ可能手机系l有问题?#8230;…你生zdq个宇宙里,可能宇宙本n有些BUG?#8230;…
所以,力做到能做到的最好就行了?/p>
—— 感谢你花费了不少旉看到q里Q但愿你没有觉得虚度?/p>
使用@RequestParamӞURL是这LQhttp://host:port/path?参数?参数?br style="margin: 0px; padding: 0px;" />
使用@PathVariableӞURL是这LQhttp://host:port/path/参数?br style="margin: 0px; padding: 0px;" />
例如Q?/p>
上面两个ҎQ访问\径分别如下:
代码如下Q?/p>
public Map<Long, String> getIdNameMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getId, Account::getUsername)); }
代码如下Q?/p>
public Map<Long, Account> getIdAccountMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getId, account -> account)); }
account -> account是一个返回本w的lambda表达式,其实q可以用Function接口中的一个默认方法代替,使整个方法更z优雅:
public Map<Long, Account> getIdAccountMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getId, Function.identity())); }
代码如下Q?/p>
public Map<String, Account> getNameAccountMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity())); }
q个Ҏ可能报错Q?strong style="margin: 0px; padding: 0px;">java.lang.IllegalStateException: Duplicate keyQ,因ؓname是有可能重复的?strong style="margin: 0px; padding: 0px;">toMap有个重蝲ҎQ可以传入一个合q的函数来解决key冲突问题Q?/p>
public Map<String, Account> getNameAccountMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2)); }
q里只是单的使用后者覆盖前者来解决key重复问题?/p>
toMapq有另一个重载方法,可以指定一个Map的具体实玎ͼ来收集数据:
public Map<String, Account> getNameAccountMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2, LinkedHashMap::new)); }
代码如下Q?/p>
public Map<Long, String> getIdNameMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getId, Account::getUsername)); }
代码如下Q?/p>
public Map<Long, Account> getIdAccountMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getId, account -> account)); }
account -> account是一个返回本w的lambda表达式,其实q可以用Function接口中的一个默认方法代替,使整个方法更z优雅:
public Map<Long, Account> getIdAccountMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getId, Function.identity())); }
代码如下Q?/p>
public Map<String, Account> getNameAccountMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity())); }
q个Ҏ可能报错Q?strong style="margin: 0px; padding: 0px;">java.lang.IllegalStateException: Duplicate keyQ,因ؓname是有可能重复的?strong style="margin: 0px; padding: 0px;">toMap有个重蝲ҎQ可以传入一个合q的函数来解决key冲突问题Q?/p>
public Map<String, Account> getNameAccountMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2)); }
q里只是单的使用后者覆盖前者来解决key重复问题?/p>
toMapq有另一个重载方法,可以指定一个Map的具体实玎ͼ来收集数据:
public Map<String, Account> getNameAccountMap(List<Account> accounts) { return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2, LinkedHashMap::new)); }
g我也是苦思良久,不经意间看到了后台的日志Q才发现是spring搞的鬹{?/span>
报错信息Q?span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #ff0000;"><No mapping found for HTTP request with URI [/sandDemo001/images/1.jpg] in DispatcherServlet with name 'spring'>
解决Ҏ1Q?span style="box-sizing: border-box; margin: 0px; padding: 0px; font-size: 14px;">在spring的配|修?/strong>web.xml下对spring?/strong>DispatcherServlethurl映射的配|,先来看看原配|?/strong>Q?/span>
解决Ҏ2Q?span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000; font-size: 14px;">在spring的配|文件中d如下一行:
<mvc:default-servlet-handler/>
注意Q需要是spring3.0.5以上版本