Java23种设计模式
定义:单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式的时候,单例对象的类必须保证只有一个实例存在。
单例模式实现方式:
分析这六种实现方式之前,先列举一个例子:
如图,我们要实现上图所示的功能:
第一步:需要为每个工人都分配一个目标仓库,然后进行出库入库操作。
public class Singleton {
class Warhouse{
List<String> goods = new ArrayList<>();
public void addGoods(){
if(goods.size()>100){
Log.w("打印","仓库满了");
}else{
goods.add("Goods"+goods.size()+1);
Log.w("打印","搬进一件货物");
}
}
public void removeGoods(){
if(!goods.isEmpty()){
goods.remove(goods.size()-1);
Log.w("打印","搬出一件货物");
}else{
Log.w("打印","仓库是空的");
}
}
}
class Worker{
Warhouse warhouse;
public Worker(Warhouse warhouse){
this.warhouse = warhouse;
}
public Warhouse getWarhouse(){
return warhouse;
}
public void doIn(){
warhouse.addGoods();
}
public void doOut(){
warhouse.removeGoods();
}
}
public void Main(){
Worker workerA = new Worker(new Warhouse());
Worker workerB = new Worker(new Warhouse());
Worker workerC = new Worker(new Warhouse());
Worker workerD = new Worker(new Warhouse());
workerB.doOut();
workerA.doIn();
workerD.doIn();
Log.w("打印","workerA搬运的仓库的容量是:"+workerA.getWarhouse().goods.size());
Log.w("打印","workerB搬运的仓库的容量是:"+workerB.getWarhouse().goods.size());
Log.w("打印","workerC搬运的仓库的容量是:"+workerC.getWarhouse().goods.size());
Log.w("打印","workerD搬运的仓库的容量是:"+workerD.getWarhouse().goods.size());
Log.w("打印","workerB和workerC仓库是否相同:"
+String.valueOf(workerB.getWarhouse() == workerC.getWarhouse()));
Log.w("打印","workerA和workerD仓库是否相同:"
+String.valueOf(workerA.getWarhouse() == workerD.getWarhouse()));
}
}
打印结果:
2019-01-08 10:52:43.263 29319-29319/fun.bingo.rxapplication W/打印: 仓库是空的
2019-01-08 10:52:43.264 29319-29319/fun.bingo.rxapplication W/打印: 搬进一件货物
2019-01-08 10:52:43.264 29319-29319/fun.bingo.rxapplication W/打印: 搬进一件货物
2019-01-08 10:52:43.264 29319-29319/fun.bingo.rxapplication W/打印: workerA搬运的仓库的容量是:1
2019-01-08 10:52:43.264 29319-29319/fun.bingo.rxapplication W/打印: workerB搬运的仓库的容量是:0
2019-01-08 10:52:43.264 29319-29319/fun.bingo.rxapplication W/打印: workerC搬运的仓库的容量是:0
2019-01-08 10:52:43.264 29319-29319/fun.bingo.rxapplication W/打印: workerD搬运的仓库的容量是:1
2019-01-08 10:52:43.264 29319-29319/fun.bingo.rxapplication W/打印: workerB和workerC仓库是否相同:false
2019-01-08 10:57:28.200 29526-29526/fun.bingo.rxapplication W/打印: workerA和workerD仓库是否相同:false
由打印结果可知这样写还是存在问题的,就是货物确实搬进仓库了,但是并没有搬进同一个仓库。
第二步:为了在同一个仓库,我们重新修改代码为:
public class Singleton {
class Warhouse{
List<String> goods = new ArrayList<>();
public void addGoods(){
if(goods.size()>100){
Log.w("打印","仓库满了");
}else{
goods.add("Goods"+goods.size()+1);
Log.w("打印","搬进一件货物");
}
}
public void removeGoods(){
if(!goods.isEmpty()){
goods.remove(goods.size()-1);
Log.w("打印","搬出一件货物");
}else{
Log.w("打印","仓库是空的");
}
}
}
class Worker{
Warhouse warhouse;
public Worker(Warhouse warhouse){
this.warhouse = warhouse;
}
public Warhouse getWarhouse(){
return warhouse;
}
public void doIn(){
warhouse.addGoods();
}
public void doOut(){
warhouse.removeGoods();
}
}
public void Main(){
Warhouse warhouse = new Warhouse();
Worker workerA = new Worker(warhouse);
Worker workerB = new Worker(warhouse);
Worker workerC = new Worker(warhouse);
Worker workerD = new Worker(warhouse);
workerB.doOut();
workerA.doIn();
workerD.doIn();
Log.w("打印","workerA搬运的仓库的容量是:"+workerA.getWarhouse().goods.size());
Log.w("打印","workerB搬运的仓库的容量是:"+workerB.getWarhouse().goods.size());
Log.w("打印","workerC搬运的仓库的容量是:"+workerC.getWarhouse().goods.size());
Log.w("打印","workerD搬运的仓库的容量是:"+workerD.getWarhouse().goods.size());
Log.w("打印","workerB和workerC仓库是否相同:"
+String.valueOf(workerB.getWarhouse() == workerC.getWarhouse()));
Log.w("打印","workerA和workerD仓库是否相同:"
+String.valueOf(workerA.getWarhouse() == workerD.getWarhouse()));
}
}
打印:
2019-01-08 10:59:18.295 29642-29642/fun.bingo.rxapplication W/打印: 仓库是空的
2019-01-08 10:59:18.295 29642-29642/fun.bingo.rxapplication W/打印: 搬进一件货物
2019-01-08 10:59:18.295 29642-29642/fun.bingo.rxapplication W/打印: 搬进一件货物
2019-01-08 10:59:18.295 29642-29642/fun.bingo.rxapplication W/打印: workerA搬运的仓库的容量是:2
2019-01-08 10:59:18.295 29642-29642/fun.bingo.rxapplication W/打印: workerB搬运的仓库的容量是:2
2019-01-08 10:59:18.295 29642-29642/fun.bingo.rxapplication W/打印: workerC搬运的仓库的容量是:2
2019-01-08 10:59:18.295 29642-29642/fun.bingo.rxapplication W/打印: workerD搬运的仓库的容量是:2
2019-01-08 10:59:18.295 29642-29642/fun.bingo.rxapplication W/打印: workerB和workerC仓库是否相同:true
2019-01-08 10:59:18.295 29642-29642/fun.bingo.rxapplication W/打印: workerA和workerD仓库是否相同:true
这样写却是没问题了,但是如果ABCD四个工人不在同一个类调用仓库,该如何处理呢?
这样就需要一个全局的仓库,在任何地方调用都指向同一个对象,所以引出今天的主题:单例模式。
一、饿汉式实现方式:饿汉说白了就是强塞给你吃,不管你需不需要。代码如下:
public class Warhouse {
private static Warhouse warhouse = new Warhouse();
private Warhouse(){}
public static Warhouse getInstanse(){
return warhouse;
}
private List<String> goods = new ArrayList<>();
public int getSize(){
return goods.size();
}
public void addGoods(){
if(goods.size()>100){
Log.w("打印","仓库满了");
}else{
goods.add("Goods"+goods.size()+1);
Log.w("打印","搬进一件货物");
}
}
public void removeGoods(){
if(!goods.isEmpty()){
goods.remove(goods.size()-1);
Log.w("打印","搬出一件货物");
}else{
Log.w("打印","仓库是空的");
}
}
}
public class Singleton {
class Worker{
Warhouse warhouse;
public Worker(Warhouse warhouse){
this.warhouse = warhouse;
}
public Warhouse getWarhouse(){
return warhouse;
}
public void doIn(){
warhouse.addGoods();
}
public void doOut(){
warhouse.removeGoods();
}
}
public void Main(){
Warhouse warhouse = Warhouse.getInstanse();
Worker workerA = new Worker(warhouse);
Worker workerB = new Worker(warhouse);
Worker workerC = new Worker(warhouse);
Worker workerD = new Worker(warhouse);
workerB.doOut();
workerA.doIn();
workerD.doIn();
Log.w("打印","workerA搬运的仓库的容量是:"+workerA.getWarhouse().getSize());
Log.w("打印","workerB搬运的仓库的容量是:"+workerB.getWarhouse().getSize());
Log.w("打印","workerC搬运的仓库的容量是:"+workerC.getWarhouse().getSize());
Log.w("打印","workerD搬运的仓库的容量是:"+workerD.getWarhouse().getSize());
Log.w("打印","workerB和workerC仓库是否相同:"
+String.valueOf(workerB.getWarhouse() == workerC.getWarhouse()));
Log.w("打印","workerA和workerD仓库是否相同:"
+String.valueOf(workerA.getWarhouse() == workerD.getWarhouse()));
}
}
打印结果如下:
2019-01-08 11:06:38.247 29912-29912/fun.bingo.rxapplication W/打印: 仓库是空的
2019-01-08 11:06:38.247 29912-29912/fun.bingo.rxapplication W/打印: 搬进一件货物
2019-01-08 11:06:38.247 29912-29912/fun.bingo.rxapplication W/打印: 搬进一件货物
2019-01-08 11:06:38.247 29912-29912/fun.bingo.rxapplication W/打印: workerA搬运的仓库的容量是:2
2019-01-08 11:06:38.249 29912-29912/fun.bingo.rxapplication W/打印: workerB搬运的仓库的容量是:2
2019-01-08 11:06:38.249 29912-29912/fun.bingo.rxapplication W/打印: workerC搬运的仓库的容量是:2
2019-01-08 11:06:38.249 29912-29912/fun.bingo.rxapplication W/打印: workerD搬运的仓库的容量是:2
2019-01-08 11:06:38.249 29912-29912/fun.bingo.rxapplication W/打印: workerB和workerC仓库是否相同:true
2019-01-08 11:06:38.249 29912-29912/fun.bingo.rxapplication W/打印: workerA和workerD仓库是否相同:true
饿汉模式优点:1. 由于用了常量,所以获取对象速度快。2. 避免了多线程的同步问题。
饿汉模式缺点:在类加载的时候,对象就完成了初始化,所以无论需不需要都占用内存,并且类加载比较慢。
二、懒汉式实现方式:懒汉式意思就是需要的时候我才去创建。仓库代码修改如下:
public class Warhouse {
private static Warhouse warhouse =null;
private Warhouse(){}
public static Warhouse getInstanse(){
if(warhouse == null){
warhouse = new Warhouse();
}
return warhouse;
}
...
}
懒汉模式优点:调用的时候才初始化,节约了资源
懒汉模式的缺点:1.第一次加载时需要初始化,反应慢。2.多线程不能正常工作。
三、枚举,修改代码如下:
public enum Warhouse {
INSTANCE;
public static Warhouse getInstanse(){
Log.w("打印","调用单例模式");
return INSTANCE;
}
...
}
优点:1.线程安全。2.具有枚举的优点。
缺点:可读性不高。
四、同步锁:用线程锁的方式解决多线程下安全问题。修改代码如下:
public class Warhouse {
private static Warhouse warhouse =null;
private Warhouse(){}
public static synchronized Warhouse getInstanse(){
if(warhouse == null){
warhouse = new Warhouse();
}
return warhouse;
}
...
}
同步锁模式下的优点:解决了线程安全问题,使多线程下也可以正常使用单例模式。
同步锁模式下的缺点:每次调用getInstance方法都需要进行同步,造成不必要的同步开销。
五、双重校验锁:使用volatile关键字和线程锁来解决多线程下安全问题。修改代码如下:
public class Warhouse {
private volatile static Warhouse warhouse =null;
private Warhouse(){}
public static Warhouse getInstanse(){
if(warhouse == null){
synchronized (Warhouse.class){
if(warhouse == null)warhouse = new Warhouse();
}
}
return warhouse;
}
...
}
双重校验锁的优点:DCL资源利用率高,第一次执行getInstance的时候对象才被实例化,类加载较快。
双重校验锁的缺点:Java5之前由于JMM存在缺陷的,使用volaile关键字回出现问题。
六、静态内部类:我之前也没这么用过,先测试下,修改代码如下:
public class Warhouse {
private Warhouse(){
Log.w("打印","初始化");
}
public static Warhouse getInstanse(){
Log.w("打印","调用单例模式");
return WarhouseHolder.warhouse;
}
private static class WarhouseHolder{
private static final Warhouse warhouse = new Warhouse();
}
...
}
调用代码如下:
public void Main(){
Log.w("打印","准备调用单例模式");
...
}
打印结果:
2019-01-08 13:42:57.991 32249-32249/fun.bingo.rxapplication W/打印: 准备调用单例模式
2019-01-08 13:42:57.991 32249-32249/fun.bingo.rxapplication W/打印: 调用单例模式
2019-01-08 13:42:57.991 32249-32249/fun.bingo.rxapplication W/打印: 初始化
...
静态内部类优点:1.无需考虑线程问题。2.调用时才创建实例,节省资源,不影响性能。