Java23种设计模式(一)单例模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yangshuaionline/article/details/86017681

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.调用时才创建实例,节省资源,不影响性能。

猜你喜欢

转载自blog.csdn.net/yangshuaionline/article/details/86017681