并发编程(二)
2022-09-22 22:47:29

hopeStation/thread-learn

并发集合

并发修改异常
java.util.ConcurrentModificationException

CopyOnWriteArrayList

https://www.cnblogs.com/myseries/p/10877420.html

JUC辅助类

BlockQueue

四组API

情况 抛出异常 不抛出异常 失败等待 时间段等待
增加操作 add() offer() put() offer(…)
取出操作 remove() poll() take() poll(..)
查看队首元素 element() peek() . .

线程池

池化技术

简单点来说,就是提前保存大量的资源,以备不时之需。

对于线程,内存,oracle的连接对象等等,这些都是资源,程序中当你创建一个线程或者在堆上申请一块内存时,都涉及到很多系统调用,也是非常消耗CPU的,如果你的程序需要很多类似的工作线程或者需要频繁的申请释放小块内存,如果没有在这方面进行优化,那很有可能这部分代码将会成为影响你整个程序性能的瓶颈。

常见的池化技术

  • 线程池
  • 连接池
  • 内存池
  • 对象池

参考自:https://blog.csdn.net/syviah/article/details/46550161

  1. 对象池
    就是提前创建很多对象,将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用

  2. 线程池

    线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:

    先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态。

    可能你也许会问:为什么要搞得这么麻烦,如果每当客户端有新的请求时,我就创建一个新的线程不就完了?这也许是个不错的方法,因为它能使得你编写代码相对容易一些,但你却忽略了一个重要的问题??性能!

    一个省级数据大集中的银行网络中心,高峰期每秒的客户端请求并发数超过100,如果为每个客户端请求创建一个新线程的话,那耗费的CPU时间和内存将是惊人的,如果采用一个拥有200个线程的线程池,那将会节约大量的的系统资源,使得更多的CPU时间和内存用来处理实际的商业应用,而不是频繁的线程创建与销毁。

  3. 内存池

    如何更好的管理在应用程序中内存的使用,同时提高内存使用的效率,这是值得每一个开发人员深思的问题。内存池(Memory pool)提供了一种比较可行的解决方案。首先是创建内存池。这个过程的主要任务是预先分配足够大的内存,形成一个初步的“内存池”。分配内存,也就是用户请求内存时,会返回内存池中一块空闲的内存,并将其标志置为已使用,当然具体细节和方法有很多。释放内存时,不是真正地调用free或是delete的过程,而是把内存放回内存池的过程。在把内存放入内存池的同时,要把标志位置为空闲。最后在应用程序结束时,要把内存池销毁。这里主要做的工作就是把内存池中的每一块内存释放。

    使用内存池的好处:

    1、减少了内存碎片的产生。这个可以从创建内存池的过程中看出。我们在创建内存池时,分配的都是一块块比较整的内存块,这样可以减少内存碎片的产生。

    2、提高了内存的使用效率。这个可以从分配内存和释放内存的过程中看出。每次的分配与释放并不是去调用系统提供的函数或是操作符去操作实际的内存,而是在复用内存池中的内存。

    缺点:

    就是很有可能会造成内存的浪费,原因也很明显,开始分配了一大块内存,不是全部都用得到的。

  4. 数据库连接池

    连接池比较典型的有oracle的连接池。

    数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的 性能低下。

    数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库联接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。

    连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

    1. 最小连接数是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费;

    2. 最大连接数是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。

线程池的作用

  • 降低资源的消耗(资源复用)
  • 提升响应速度(拿来就用)
  • 方便管理

线程可以复用,可以控制最大并发数!

线程池必会

3大方法,7大参数,4种拒绝策略

三大方法

1
2
3
4
5
6
//单个线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
//创建一个固定的线程池的大小
ExecutorService threadPool = Executors.newFixedThreadPool(5);
//可伸缩的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static ExecutorService newSingleThreadExecutor() {
return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}
public static ExecutorService newFixedThreadPool(int var0) {
return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}


public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

七大参数

  1. corePoolSize: 核心线程数

  2. maximumPoolSize允许创建的最大线程数

  3. keepAliveTime: 这个看一下单词也能猜出它的主要作用了吧?当线程池的线程数大于核心线程数的时候,线程空闲时间超过这个时间后还没有任务给它做的话就会关闭。小于等于核心线程数的时候不会被关闭,但是可以通过public void allowCoreThreadTimeOut(true)来设置使其可以被关闭。

  4. TimeUnit unit:keepAliveTime的时间单位(作转换用)
    this.keepAliveTime = unit.toNanos(keepAliveTime);

  5. BlockingQueue workQueue: 任务队列

  6. ThreadFactory threadFactory: 生成线程的工厂,另一个重载的方法就是带有这个参数的。

  7. RejectedExecutionHandler handler 当线程池满了,但还有新任务提交的时候,就会采用这个策略来进行处理 ThreadPoolExecutor 中已经定义好四个实现类了:

四种拒绝策略

  • public static class AbortPolicy implements RejectedExecutionHandler;

    直接抛出 RejectedExecutionException 异常,这个是默认的处理策略。

  • public static class CallerRunsPolicy implements RejectedExecutionHandler;

    如果线程池没有被关闭,就交由提交任务的线程来执行。

  • public static class DiscardOldestPolicy implements RejectedExecutionHandler

    这个策略就是讲等待队列头的任务给扔掉,然后将这个新任务提交到等待队列中。

  • public static class DiscardPolicy implements RejectedExecutionHandler

    丢弃任务,不做任何处理。