Java中的 工厂模式 和 线程池

1. 工厂模式

工厂模式是一个设计模式,可以根据具体情景需求,自己写一个工厂类来满足需求。工厂模式是用来填补 构造方法的 局限性。对于一些需求多样化的场景,通过一个类的构造方法难以满足需求,这时就可以通过工厂模式来满足多样化的需求。

例如,有一个任务,任务是 描述一个点在 笛卡尔坐标系中 和 极坐标中 的位置,位置变量都是int型。希望可以定义一个类 来完成这个任务。代码如下:

从图中可以看出,想通过SetPoint这一个类来完成这个任务是行不通的。

因为想要描述一个点在不同坐标系的位置时,首先要new出当前类的对象,就涉及到类的构造方法的调用。由于要描述的是点在不同坐标系中的位置,就需要用到不同的构造方法,但如果这两种构造方法的参数列表相同的话,构造方法就无法正常构造,没有达到方法重载的标准,也因此这一个类无法完成这个任务。

总的来说就是,一个类的构造方法可支持的功能太少,局限性比较大,无法满足多样化的需求。因此,可以使用工厂模式,通过工厂类调用工厂类的工厂方法,来满足需求。对于一个工厂类的不同的工厂方法,可以通过不同的方法名进行区分,这样就不用受 方法重载(参数列表不同)的条件限制了。

为了完成上述任务,我们可以自己写一个工厂类,在工厂类中写不同的普通方法(工厂方法),进而去满足需求。代码如下:

class FactoryPoint{
    //设置 笛卡尔坐标点
    public static Point makePointAB(int a,int b){
        Point point = new Point();
        point.setPointA(a);
        point.setPointB(b);
        return point;
    }

    //设置 极坐标点
    public static Point makePointCD(int c,int d){
        Point point = new Point();
        point.setPointC(c);
        point.setPointD(d);
        return point;
    }
}

这样就能完成上述任务了,并且即使有更多种任务,可以继续定义其他普通方法来满足需求,只用区分方法名就可以了。

通过工厂模式来完成一些任务,就不用new来构造对象了,而是调用某个工厂类的工厂方法来完成相应任务即可。

2.线程池

将工厂模式与线程池放在一起说明,是因为构造线程池对象就需要用到工厂模式。因为线程池中可以容纳多个线程,工厂模式正好可以满足多个线程的各种需求。

【什么是线程池】

“池”意味着可以容纳东西,线程池 意思就是 线程池 可以存放线程。

【为什么要用线程池】

对于多进程并发编程,每个进程占用各种资源,创建,销毁,调度多个进程意味着巨大的系统开销。因此,在JAVA中引入了多线程并发编程,开销更小。但若在一些情境下,要频繁的创建,销毁线程,这造成的系统开销也是不容忽视的。因此,在一些情景中,使用线程池来减小系统开销,提高效率。

【为什么有了线程池,系统的开销会更小】

有了线程池,当我们创建了一个线程时,线程池会为我们提前创建好第2,3,4....个线程,这样后续若还需要创建新的线程,就不用再创建了,直接让线程池中的线程去执行任务。

线程池中去拿线程来使用,是纯用户态操作;而创建新的线程内核态+用户态操作,要调用系统的API,到系统内核中去完成相应操作。如果没有线程池,每创建一个线程,都需要到内核态进行操作,但系统内核要为所有进程线程服务,当系统内核去完成创建线程任务时,可能也会为其他进程线程服务,这个是不可控。而纯用户态操作的话,是可控的,直接去线程池中使用已经创建好的线程。

 (就比如,我要去银行取钱,我可以选择去ATM机取,也可以去柜台办理。如果我选择去AYM机取,我直接一顿操作,几分钟就完成了,就可以走了;但是,如果我到柜台办理的话,我刚准备按要求输密码时,柜台人员被叫走忙别的事情去了,我就得等,这个过程就让我浪费了不必要的时间。这个例子中,我去ATM机办理,就相当于用户态操作,柜台办理相当于内核态操作,用户态操作可以由我自己掌控,但内核态操作,我们没办法干涉。因此,有了线程池,系统的效率会更高。)

Java标准库中,有写好的线程池,可以直接使用。

线程池通过工厂模式来完成线程池对象的构造,Excutors这个工厂类中,常用的工厂方法有上述两个,分别为newCachedThreadPool()newFixedThreadPool()方法。其中,newCachedThreadPool()方法构造出来的线程池对象的特点是,线程池中的线程数目是能够动态适应的。随着往线程池中添加任务,这个线程池中的线程会根据需要自动被创建出来,创建出来线程之后也不会立刻销毁,会在池子中保留一段时间以备需要。

 newFixedThreadPool()方法构造出来的线程池对象的特点是,线程池中的线程数目是固定的,在构造线程池对象时直接指定数量,如上述代码中指定了9。表示当前构造出来的线程池可以有9个线程。

 newFixedThreadPool(n),这里的线程数量n应该设置多少才是合适的呢?

这里的n并没有一个固定的标准,而是根据具体的代码来衡量的

线程所执行的代码可以分为两种:CPU密集型(代码的主要逻辑是在进行 逻辑判断或运算),I/O密集型(代码主要是在进行I/O操作)。一般所执行的代码并不是单纯哪一类的代码,而是二者都含有。

假设一个CPU的逻辑核心数是N

如果线程所执行的代码是CPU密集型的话,n就不应该大于N,因为N已经是CPU的极限了,若n超过N了,更多的线程反而会增大系统调度的开销。

如果线程所执行的代码是I/O密集型的话,这时CPU负载并不对大,线程数量n是可以多大于CPU逻辑核心数N的。

具体n确定为多少,应该要根据实际代码进行测试,选择最适合当前代码情况的值。

3. 关于线程池的其他内容

上述的工厂方法  本质上是对ThreadPoolExcutor类的封装,这些工厂方法给这个类提供了不同的参数来构造线程池。ThreadPoolExcutor类的核心方法有两个,分别是 构造方法 和 添加任务方法。

这个类的构造方法有四个版本,如下图:

每个构造方法有很多参数,图中最后一个构造方法的参数最多,且包含有上面三个构造方法的参数。 下面解释参数的意义:

corePoolSize:表示构造出的线程池核心线程数目

maximumPoolSize:线程池最大线程数目

这两个参数表示,当前线程池的线程数目可以动态变化,范围在[corePoolSize,maximumPoolSize]

BlockingQueue<Runnable> workQueue: 阻塞队列,用来存放线程池中的任务

ThreadFactory threadFactory:ThreadFactory作为工厂类,用来完成线程的创建,使用工厂类完成线程的创建是为了在线程创建的过程中完成线程相关属性的设置。

RejectedExcutionHandler handler:线程池的拒绝策略

一个线程池可以存放线程的数目是有限的,如果线程池中存放的线程数目已经达到了上限线程池的拒绝策略会有做出相应的动作,不同的拒绝策略有不同的效果

【拒绝策略】有以下几种:

AbortPolicy:直接抛出RejectedExcutionException异常 。

CallerRunsPolicy:新添加的任务,由添加任务的线程去负责执行

 比如,别人请我帮忙,我没空,只能他自己去做这件事情。

DiscardOldestPolicy丢弃任务队列中最老的任务

比如,别人请我帮忙,但我还有好几个事情做,没空,但我推掉了我的一个事情,去帮他忙

DiscardPolicy: 丢弃新添加的任务

比如,别人请我帮忙,我没空,帮不了,他也做不了这个事情,因此这个事情就直接被放弃不做了

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值