Posts /

设计模式

30 Jul 2020

设计模式


​ 不能滥用static。

​ 正常的设计模式上不能随便使用static变量,应该先懂得如何编写足以证明“赋值结果冲突、混乱”的测试用例,然后再使用static变量[1]


单例模式

单例模式意味着该类只会有一个实例,而且自行实例化并向整个系统提供该实例。

五种实现方式:懒汉、饿汉、双重锁、静态内部类、Enum

1. 懒汉单例

懒汉单例,在需要用到该实例的时候才会实例化,能够延迟实例化,节约资源。如下简单的懒汉单例是非线程安全的。因为在进入singleton == null之后,有可能被其他线程中断,从而实例两个对象。

class LazySingleton {
    private static LazySingleton singleton;

    private LazySingleton() {
    }
    public static LazySingleton getLazySingleton() {
        if (singleton == null) {
            singleton = new LazySingleton();
        }
        return singleton;
    }
}

懒汉单例满足线程安全的最简单的方式就是在getSingleton方法加上synchronized修饰,使得该方法同步。但是该方式会对LazySingleton加类锁,所得粒度比较粗,并发性降低。

2. 饿汉单例

饿汉单例在类加载、加载静态数据的时候就会实例。这样虽然是线程安全的但是享受不到延迟实例化带来的好处(节约资源)。

class EagerSingletonCocurrency {
    private static EagerSingletonCocurrency singleton = new EagerSingletonCocurrency();
    private EagerSingletonCocurrency() {
    }
    public static EagerSingletonCocurrency getEagerSingletonCocurrency() {
        return singleton;
    }
}

3. 双重锁机制

双重锁机使得线程安全,同时也能享受延迟实例化的好处。

class DoubleLockSingleton{
     //volatile 关键字确保:当singleton变量被初始化成DoubleLockSingleton实例时,多个线程正确地处理singleton变量。
    private static volatile DoubleLockSingleton singleton;
    private DoubleLockSingleton(){}
    public static getDoubleLockSingleton(){
        //防止在singleton != null的时候也会加锁。
        if(singleton == null){
            synchronized(DoubleLockSingleton.class){
             //当==null之后进入同步代码块,再次判断时候为null。
            // 因为在进入同步之前有可能有线程中断该位置,已经实例化了单例。
                if(singleton ==null){
                    singleton = new DoubleLockSingleton();
                }
            }
        }
    }
}

该段出处:CS-Notes博客的单例(Singleton)

singleton采用 volatile 关键字修饰也是很有必要的, singleton= new DoubleLockSingleton(); 这段代码其实是分为三步执行:

  1. 为 singleton分配内存空间
  2. 初始化 singleton
  3. 将 singleton指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getDoubleLockSingleton() 后发现 singleton不为空,因此返回 singleton,但此时 singleton还未被初始化。

使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

4. 静态内部类

当StaticSingleton被加载的时候,静态内部类还不会被加载。只有当执行getStaticSingleton()方法的时候才会被加载。同时JVM保证线程安全和只被实例化一次。

class StaticSingleton{
    private StaticSingleton(){}
    static class StaticSingletonHolder{
        // 静态内部类,
        private static StaticSingleton singleton = new StaticSingleton();
    }
    public StaticSingleton getStaticSingleton(){
        return StaticSingletonHolder.singleton;
    }
    
}

当 StaticSingleton类被加载时,静态内部类 StaticSingletonHolder没有被加载进内存。只有当调用 getStaticSingleton() 方法从而触发 StaticSingletonHolder.INSTANCE 时 StaticSingletonHolder才会被加载,此时初始化 singleton实例,并且 JVM 能确保 INSTANCE 只被实例化一次。

这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。

5. 枚举实现

/**
 * 枚举单例
 * 
 * @author: Maple Chan
 * @date: 2020-07-31 22:27:14
 * @version: 0.0.1
 */
public enum EnumSingleton {
    INSTANCE;

    private User user;
    private String notice;

    public void doSomething() {
        System.out.println("Do something!");
        System.out.println(notice);
    }
    public User getUser() {
        return user;
    }
    /**
     * 枚举类被初始化时调用构造函数.每个构造函数将以成员声明顺序被调用, 不管实际引用和使用哪些成员.
     */
    private EnumSingleton() {
        user = new User();
        notice = "Life is fantastic!";
    }
    
    class User {
        private String name;
        private User(){
            name = "default name";
        }
        public String getUserName() {
            return name;
        }
    }

    public static void main(String[] args) {
        EnumSingleton.INSTANCE.doSomething();
        System.out.println(EnumSingleton.INSTANCE.getUser().getUserName());
    }

}

/*
输出:
Do something!
Life is fantastic!
default name
*/

单例的枚举实现在Effective Java一书中提到。因为其功能完善,使用简洁,无偿地提供了序列化机制,在面对复杂的序列化或者反射攻击时任然可以绝对防止多次实例化等优点,被作者所推崇。

模板模式

Java设计模式—模板方法模式

package Java_test;
/**
	模板设计模式,定义一个抽象类,其中有抽象方法和具体方法,具体方法调用抽象方法。抽象方法可以有所继承的子类进行自定义,这就是模板设计模式。
	不同的子类根据子类的不同定义,执行不同的算法。(也可以核心算法固定,中间可以定义一些自定义算法,例如增减排序)
*/
public class TemplatePattern {
    
    public static void main(String[] args) {
        ConcreteClass concreteClass = new ConcreteClass();
        concreteClass.doTemplate();
        TemplateAbstract template = new TemplateAbstract(){
            @Override
            protected void doSomething() {
                System.out.println("doSomething");
            }
            @Override
            protected void customFunction() {
                System.out.println("customFunction");
            }
        };
        template.doTemplate();
    }
}
abstract class TemplateAbstract{
    protected abstract void doSomething();
    protected abstract void customFunction();
    protected void doTemplate(){
        doSomething();
        customFunction();
    }
}
class ConcreteClass extends TemplateAbstract{
    @Override
    protected void doSomething() {
        System.out.println("自定义doSomthing");
    }
    @Override
    protected void customFunction() {
        System.out.println("自定义customFunction");
    }
}

生产者消费者模式

生产者和消费者指的是两个不同的线程类对象,操作同一资源的情况。

结构图:

1596028363129

package javatest.pattern;

import java.util.LinkedList;
import java.util.Random;

/**
 * @description: 生产者消费者设计模式
 * @modifyContent:
 * @author: Maple Chan
 * @date: 2020-07-29 20:31:06
 * @version: 0.0.1
 */
public class ProducerAndConsumer {

    public static void main(String[] args) {
        new Producer().start();
        new ConsumerA().start();
        new ConsumerB().start();
    }
}

class Producer extends Thread {
    private void produce() {
        Goods store = Goods.getInstance();

        // do produce
        long product = new Random().nextLong();
        // put into store
        synchronized (store) {
            System.out.println("生产产品:" + product);
            Goods.goodList.add(product);
        }
    }

    @Override
    public void run() {
        int count = 3;
        while (true) {
            long sleepTime = new Random().nextInt(10) * 100;
            try {
                Thread.sleep(sleepTime);
            } catch (Exception e) {
                // TODO: handle exception
            }
            this.produce();
            if (count-- < 0) {
                break;
            }
        }

        return;
    }
}

class ConsumerA extends Thread {
    private void consumer() {
        Goods store = Goods.getInstance();

        synchronized (store) {
            Long product = Goods.goodList.getFirst();
            Goods.goodList.remove(product);
            System.out.println("消费产品:" + product);
        }
    }
    @Override
    public void run() {
        while (true) {
            long sleepTime = new Random().nextInt(10) * 100;

            try {
                Thread.sleep(sleepTime);
            } catch (Exception e) {
                // TODO: handle exception
            }
            if (Goods.goodList.size() > 0) {
                this.consumer();
            }
        }
    }
}

class ConsumerB extends Thread {
    private void consumer() {
        Goods store = Goods.getInstance();

        synchronized (store) {
            Long product = Goods.goodList.getFirst();
            Goods.goodList.remove(product);
            System.out.println("消费产品:" + product);
        }
    }
    @Override
    public void run() {
        while (true) {
            long sleepTime = new Random().nextInt(10) * 120;

            try {
                Thread.sleep(sleepTime);
            } catch (Exception e) {
                // TODO: handle exception
            }
            // 库存有东西才会进行消费
            if (Goods.goodList.size() > 0) {
                this.consumer();
            }
        }

    }
}

class Goods {

    public static LinkedList<Long> goodList;
    private static Goods singletonGoods;
    private Goods() {
    }
    static {
        goodList = new LinkedList<>();
        singletonGoods = new Goods();
    }
    /**
     * 单例
     */
    public static Goods getInstance() {
        return singletonGoods;
    }
}

输出如下所示:

/*
生产产品:6805117464760743258
消费产品:6805117464760743258
生产产品:1599562522443449507
生产产品:1041166688112309013
消费产品:1599562522443449507
生产产品:124370676431480001
消费产品:1041166688112309013
生产产品:7414022159762949506
消费产品:124370676431480001
消费产品:7414022159762949506

代码中生产5个数据,之后消费将一直等待。
*/