实习笔记(二)设计模式和java工具

一 设计模式

下面举几个常用设计模式:单例模式;工厂模式(抽象工厂模式);观察者模式;建造者模式;代理模式

有20多种设计模式,最基础的是上面这几种,很多其实使他们的变形和扩展。

综述:我认为,设计模式就是在代码骨架,目的是让自己的代码高内聚低耦合,可读性强,可复用性强,可能一开始构思会慢,先写很多借口或与功能无关的代码,但是却能在日后数倍的减少代码量和维护代码产生工作量。一般我在开发中如果有冗余代码,或者多个文件中有雷同的代码,就是没用好设计模式。代码中没有冗余代码,整个工程中没有相同逻辑和功能的代码,而且高内聚低耦合,使他人容易阅读和扩展日后的需求,而且可移植可复用,就是好的设计模式。
接口的定义与实现分离,将逻辑层抽象出来,对象的动态实例化(也就是除了参数的传递,将对象功能的实现放在最后)

  1. 单例模式
    单例模式是最简单模式,就是一个类在内存中只有一个实例。
    在类内实例化,并定义为私有。
public class  Singleton implements Runnable{
    public static int num = 0;
    private static final Singleton singleton = new Singleton();

    public Singleton(){
        num++;
    };
    public static Singleton getSingleton(){
        return singleton;
    }
    @Override
    public void run(){
        System.out.println(num);
    }
}

 @Test
    public  void testSingleTon(){
        for(int i =0; i<100;i++) {
            Thread thread = new Thread(Singleton.getSingleton());
            thread.run();
        }
    }
    
    #上面代码输出是100个1,因为该类只有一个实例化的对象,只实例化一次,所有类静态变量num一直是1
    
    #如果我改成如下代码 
    public class  Singleton implements Runnable{
    public static int num = 0;
    public Singleton(){
        num++;
    };
    public static Singleton getSingleton(){
        return new Singleton();
    }
    @Override
    public void run(){
        System.out.println(num);
    }
}
#单元测试输出1-100

因为多线程每个线程都有一次实例化,这就是多例模式和单例模式的区别。单例只允许内存中存在一个实例化的对象,一直驻留内存。单例模式可在系统设置全局访问点,优化和共享资源访问。

主义:单例模式必须是线程安全的,比如全局唯一序列。
要保证线程线程同步,高并发下内存中可能出现多实例。所有要加互斥锁。使用线程安全容器Vector替代ArrayList,在方法前加同步互斥锁synchronized
  1. 工厂模式
    就是创建一个实例化不同子类的接口,将一个类的实例化延迟到其子类。工厂模式通常对应着抽象类,将工厂定义为抽象类,里面写好所有子类的通用方法,让不同的子类继承。但是并不常用,每个子类都要继承,子类较多也很麻烦,而且如果工厂需要修改,会波及所有子类。
    抽象工厂模式,解决了只要给出需要的产品,给工厂就能生产,无需知道具体细节,每个产品有各自的实现类,横向扩展容易,可以无限量种产品,但是抽象类一旦定义,修改很难,也就是约束很难修改。

  2. 观察者模式
    也叫(Publish/subscribe)发布订阅模式
    定义对象之间一对多的依赖关系,使得当一个对象改变,所有依赖它的对象都得到通知并被自动更新
    被观察者定义一个线程安全数组,存放它的观察者,并能增删观察者,并发送消息。

注意:如果是顺序向观察者发送消息,一个观察者阻塞会影响整个执行效率,要用异步方式(需要考虑线程安全和队列问题,一定要懂Message Queue消息队列)。
  1. 建造者模式

当使用的工具相同,相同方法,不同的执行顺序,会产生不同结果时,考虑采用建造者模式。需要在建造者类中用有序集合存放每个实现类的执行序列,将每个产品的序列和参数传递给Builder,并有它调用不同产品的实现类,用户只需要告诉Builder序列和名称,无需知道具体实现。跟工厂模式类似,但是关注点在序列,而非实现的封装。

  1. 代理模式

代理模式是java中最常用的,面向切面的编程,即AOP就是代理模式,这也是最难理解的模式。代理,就想到游戏带练,不光如此。把我想执行的一个操作交给定义的代理服务,他会有berfore和after,即该操作前后的一些必要工作。代理类与委托类有同样的接口,代理类负责预处理消息、过滤、权限验证、日志记录等必要操作。
有静态代理和动态代理,静态代理每个代理类只为一个接口服务,多代理肯定会有重复代码,动态代理是程序运行时,由反射机制动态创建。ava.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。

二 工具

1. 异步线程池

这篇博客很详细
https://blog.csdn.net/tuke_tuke/article/details/51353925?utm_source=blogxgwz0
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
submit和execute
1、接收的参数不一样 submit()可以接受runnable无返回值和callable有返回值
execute()接受runnable 无返回值

2、submit有返回值,而execute没有

Method submit extends base method Executor.execute by creating and returning a Future that can be used to cancel execution and/or wait for completion.

用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么。

3、submit方便Exception处理

There is a difference when looking at exception handling. If your tasks throws an exception and if it was submitted with execute this exception will go to the uncaught exception handler (when you don’t have provided one explicitly, the default one will just print the stack trace to System.err). If you submitted the task with submit any thrown exception, checked or not, is then part of the task’s return status. For a task that was submitted with submit and that terminates with an exception, the Future.get will rethrow this exception, wrapped in an ExecutionException.

意思就是如果你在你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

ThreadPoolExecutor(int corePoolSize,
                        int maximumPoolSize,
                        long keepAliveTime,
                        TimeUnit unit,
                        BlockingQueue workQueue) 
corePoolSize: 核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。

maximumPoolSize:线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。

keepAliveTime:非核心线程的闲置超时时间,超过这个时间就会被回收。

unit:指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。

workQueue:线程池中的任务队列.常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue
import java.util.concurrent.*;
public class ExecutorServiceTest {
    ExecutorService threadPool =  new ThreadPoolExecutor(
            1, 4,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(1024)
    );

    public void asyncHandle(LimitQueue limitQueue){
        threadPool.execute(()->{
            System.out.println("consumer"+limitQueue.poll());
        });
    }
}

public class Producer implements Runnable {

    public Producer(LimitQueue limit){
        Singleton.getSingleton();
        limit.offer(Singleton.getNum());
    }

    @Override
    public void run(){
        System.out.println("produce"+ Singleton.getNum());
    }
}

public class Consumer implements Runnable{

   private LimitQueue limitQueue;
   
    public Consumer(LimitQueue limit){
       this.limitQueue = limit;
    }

    @Override
    public void run(){
        ExecutorServiceTest executorServiceTest = new ExecutorServiceTest();
        executorServiceTest.asyncHandle(limitQueue);
    }
}

public class TestProducerConsumer{
    private LimitQueue limitQueue = new LimitQueue<>(10);
    public void start(){
        Producer producer = new  Producer(limitQueue);
        Thread t = new Thread(producer);
        t.start();

        Consumer consumer = new Consumer(limitQueue);
        Thread t2 = new Thread(consumer);
        t2.start();
    }

    public static void main(String args[]){
        int i = 0;
       while (i<100) {
           i++;
           TestProducerConsumer testProducerConsumer = new TestProducerConsumer();
           testProducerConsumer.start();
       }
    }
}

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

@Slf4j
public class LimitQueue implements Serializable {

    /**
     * 队列长度
     */
    private int limit;

    @Getter
    private Queue queue;

    public LimitQueue(int limit) {
        this.limit = limit;
        this.queue = new LinkedBlockingQueue<>(limit);
    }

    /**
     * 入列:当队列大小已满时,把队头的元素poll掉
     */
    @JSONField(serialize = false)
    public void offer(E e) {
        if (null == e) {
            return;
        }
        if (queue.size() >= limit) {
            log.debug("offer poll size = {} limit = {}", queue.size(), limit);
            queue.poll();
        }
        queue.offer(e);
    }

    @JSONField(serialize = false)
    public E poll() {
        return queue.poll();
    }

    @JSONField(serialize = false)
    public int getLimit() {
        return limit;
    }

    @JSONField(serialize = false)
    public int size() {
        return queue.size();
    }

    @JSONField(serialize = false)
    public List toList() {
        return new ArrayList<>(queue);
    }
}


上面演示了一个简单的生产者消费者模式,生产者和消费者都是实现了Runnable接口,Thread类的start方法会执行多线程,上面消费者使用异步线程池跑,可以看到producer不是顺序读取队列的。
如果消费者不使用ThreadPoolExecutor,那么会看到生产者和消费者的序号是对应的。

#去掉异步线程池,先进先出,有序
produce1
produce2
produce3
produce4
consumer1
consumer3
consumer2
consumer4
produce5
consumer5
produce6
consumer6
produce7
......
#消费者使用异步线程池
produce34
produce35
produce36
consumer19
consumer5
consumer22
consumer2
consumer25
consumer35
consumer11
consumer23
consumer27
consumer7
consumer14
consumer16
consumer29
consumer32
consumer10

上面的例子是在实习是遇到的,需求是读取视频流,调用检测跟踪算法,视频流是实时的,不是TOC的网站接口与用户交互。所有面对一秒多帧的视频流,将解码器对象封装成生产者,消费者调用检测算法,流处理都要做成多线程,一帧出错不至于影响后续,异步工具用在将处理后的数据发送到服务器端。在RPC调用的部分使用异步线程池,就是保证消费者在数据后处理时,不会因为RPC延迟影响数据的处理,如果是串行处理,很容易出问题,线程池开的较大,未处理完成的也会放到阻塞队列,线程生存时间也会及时杀死无效进程。

你可能感兴趣的:(实习笔记(二)设计模式和java工具)