Update Java 并发.md

增加队列同步器的说明
This commit is contained in:
HuaHero 2023-11-09 15:40:06 +08:00 committed by GitHub
parent 8d502485e9
commit 26e8134eec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -213,7 +213,8 @@ public void run() {
**关于线程池**
【线程池的意义】
线程池可以有效地管理线程:它可以**管理线程的数量**,可以避免无节制的创建线程,导致超出系统负荷直至崩溃。它还可以**让线程复用**,可以大大地减少创建和销毁线程所带来的开销。
【**线程池的核心参数**】 线程池需要依赖一些参数来控制任务的执行流程,其中最重要的参数有corePoolSize核心线程数、workQueue等待队列、maxinumPoolSize最大线程数、handler拒绝策略、keepAliveTime空闲线程存活时间
【**线程池的核心参数**】
线程池需要依赖一些参数来控制任务的执行流程,其中最重要的参数有corePoolSize核心线程数、workQueue等待队列、maxinumPoolSize最大线程数、handler拒绝策略、keepAliveTime空闲线程存活时间
【线程池的运作步骤】
当我们向线程池提交一个任务之后,线程池按照如下步骤处理这个任务:
1. 判断线程数是否达到corePoolSize,若没有则新建线程执行该任务,否则进入下一步。
@ -221,7 +222,7 @@ public void run() {
3. 判断线程数是否达到maxinumPoolSize,如果没有则新建线程执行任务,否则进入下一步。
4. 采用初始化线程池时指定的拒绝策略,拒绝执行该任务。
5. 新建的线程处理完当前任务后,不会立刻关闭,而是继续处理等待队列中的任务。如果线程的空闲时间达到了keepAliveTime,则线程池会销毁一部分线程,将线程数量收缩至corePoolSize。 第2步中的队列可以有界也可以无界。若指定了无界的队列,则线程池永远无法进入第3步,相当于废弃了maxinumPoolSize参数。这种用法是十分危险的,如果任务在队列中产生大量的堆积,就很容易造成内存溢出。
【线程池创建工具--建议用**ThreadPoolExecutor**】
【线程池创建工具--建议用**ThreadPoolExecutor**】
JDK为我们提供了一个名为Executors的线程池的创建工具,该工具创建出来的就是带有无界队列的线程池,所以一般在工作中我们是不建议使用这个类来创建线程池的。 第4步中的拒绝策略主要有4个让调用者自己执行任务、直接抛出异常、丢弃任务不做任何处理、删除队列中最老的任务并把当前任务加入队列。这4个拒绝策略分别对应着RejectedExecutionHandler接口的4个实现类,我们也可以基于这个接口实现自己的拒绝策略。 在Java中,线程池的实际类型为ThreadPoolExecutor,它提供了线程池的常规用法。该类还有一个子类,名为ScheduledThreadPoolExecutor,它对定时任务提供了支持。在子类中,我们可以周期性地重复执行某个任务,也可以延迟若干时间再执行某个任务。
在Java中正常创建线程池应该使用ThreadPoolExecutor类。虽然Executors类可以方便地创建线程池但它使用的默认参数可能不适合所有情况。例如Executors.newFixedThreadPool方法创建的线程池默认使用无界队列可能会导致内存溢出。此外Executors类隐藏了线程池的细节使得无法对其进行更灵活的配置和优化。因此建议使用ThreadPoolExecutor类手动创建线程池并根据具体情况进行参数配置。
【线程池的生命周期状态】 线程池的生命周期包含5个状态RUNNING、SHUTDOWN、STOP、TIDING、TERMINATED。这5种状态的状态值分别是-1、0、1、2、3。在线程池的生命周期中,它的状态只能由小到大迁移,是不可逆的。 1. RUNNING表示线程池正在运行。 2. SHUTDOWN执行shutdown()时进入该状态,此时队列不会清空,线程池会等待任务执行完毕。 3. STOP执行shutdownNow()时进入该状态,此时现线程池会清空队列,不再等待任务的执行。 4. TIDING当线程池及队列为空时进入该状态,此时线程池会执行钩子函数,目前该函数是一个空的实现。 5. TERMINATED钩子函数执行完毕后,线程进入该状态,表示线程池已经死亡。
@ -755,17 +756,25 @@ after
![线程状态转换图](..\assets\1641890623956-ani--线程状态转换.jpg)
## 七、J.U.C - AQS(AbstractQueuedSynchornizer)
**关于AQS**
AbstractQueuedSynchronizer是**队列同步器,是用来构建锁的基础框架**,Lock实现类都是基于AQS实现的。AQS是基于**模板方法模式进行设计的,所以锁的实现需要继承AQS并重写它指定的方法**。A**QS内部定义了一个FIFO的队列来实现线程的同步,同时还定义了同步状态来记录锁的信息**。 AQS的模板方法,将管理同步状态的逻辑提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。以独占式获取同步状态为例,它的大致流程是:
1. 尝试以独占方式获取同步状态。
2. 如果状态获取失败,则将当前线程加入同步队列。
3. 自旋处理同步状态,如果当前线程位于队头,则唤醒它并让它出队,否则使其进入阻塞状态。 其中,有些步骤无法在父类确定,则提炼成空方法留待子类实现。例如,第一步的尝试操作,对于公平锁和非公平锁来说就不一样,所以子类在实现时需要按照场景各自实现这个方法。 AQS的同步队列,是一个双向链表,AQS则持有链表的头尾节点。对于尾节点的设置,是存在多线程竞争的,所以采用CAS的方式进行修改。对于头节点设置,则一定是拿到了同步状态的线程才能处理,所以修改头节点不需要采用CAS的方式。 AQS的同步状态,是一个int类型的整数,它在表示状态的同时还能表示数量。通常情况下,状态为0时表示无锁,状态大于0时表示锁的重入次数。另外,在读写锁的场景中,这个状态标志既要记录读锁又要记录写锁。于是,锁的实现者就将状态表示拆成高低两部分,高位存读锁、低位存写锁。 加分回答 同步状态需要在并发环境下修改,所以需要保证其线程安全。由于AQS本身就是锁的实现工具,所以不适合用锁来保证其线程安全,因为如果你用一个锁来定义另一个锁的话,那干脆直接用synchronized算了。实际上,同步状态是被volatile修饰的,该关键字可以保证状态变量的内存可见性,从而解决了线程安全问题。(来自https://www.nowcoder.com/exam/interview/75826387/test?paperId=50270024)
java.util.concurrentJ.U.C大大提高了并发性能AQS 被认为是 J.U.C 的核心。
1. **原子类** 从JDK 1.5开始,并发包下提供了atomic子包,这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。在atomic包里一共提供了17个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新引用类型、原子更新属性、原子更新数组。
2. **锁** 从JDK 1.5开始,并发包中新增了Lock接口以及相关实现类用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了隐式获取释放锁的便捷性,但是却拥有了多种synchronized关键字所不具备的同步特性,包括:**可中断地获取锁**、**非阻塞地获取锁**、**可超时地获取锁**。
2. **锁** 从JDK 1.5开始,并发包中新增了Lock接口以及相关实现类用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了隐式获取释放锁的便捷性,但是却拥有了多种synchronized关键字所不具备的同步特性,包括:**可中断地获取锁**、**非阻塞地获取锁**、**可超时地获取锁**。
3. **线程池** 从JDK 1.5开始,并发包下新增了内置的线程池。其中,ThreadPoolExecutor类代表常规的线程池,而它的子类ScheduledThreadPoolExecutor对定时任务提供了支持,在子类中我们可以周期性地重复执行某个任务,也可以延迟若干时间再执行某个任务。此外,Executors是一个用于创建线程池的工具类,由于该类创建出来的是带有无界队列的线程池,所以在使用时要慎重。
3. **线程池** 从JDK 1.5开始,并发包下新增了内置的线程池。其中,**ThreadPoolExecutor**类代表常规的线程池,而它的子类ScheduledThreadPoolExecutor对定时任务提供了支持,在子类中我们可以周期性地重复执行某个任务,也可以延迟若干时间再执行某个任务。此外,Executors是一个用于创建线程池的工具类,由于该类创建出来的是带有无界队列的线程池,所以在使用时要慎重。
4. **并发容器** 从JDK 1.5开始,并发包下新增了大量高效的并发的容器,这些容器按照实现机制可以分为三类。第一类是以**降低锁粒度来**提高并发性能的容器,它们的类名以Concurrent开头,如ConcurrentHashMap。第二类是采用**写时复制技术实**现的并发容器,它们的类名以CopyOnWrite开头,如CopyOnWriteArrayList。第三类是采用**Lock实现的阻塞队列,内部创建两个Condition分别用于生产者和消费者的等待**,这些类都实现了BlockingQueue接口,如ArrayBlockingQueue。
4. **并发容器** 从JDK 1.5开始,并发包下新增了大量高效的并发的容器,这些容器按照实现机制可以分为三类。第一类是以**降低锁粒度来**提高并发性能的容器,它们的类名以Concurrent开头,如ConcurrentHashMap。第二类是采用**写时复制技术实**现的并发容器,它们的类名以CopyOnWrite开头,如CopyOnWriteArrayList。第三类是采用**Lock实现的阻塞队列,内部创建两个Condition分别用于生产者和消费者的等待**,这些类都实现了BlockingQueue接口,如ArrayBlockingQueue。
5. **同步工具** 从JDK 1.5开始,并发包下新增了几个有用的并发工具类,一样可以保证线程安全。其中,Semaphore类代表信号量,可以控制同时访问特定资源的线程数量CountDownLatch类则允许一个或多个线程等待其他线程完成操作CyclicBarrier可以让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才会继续运行。
5. **同步工具** 从JDK 1.5开始,并发包下新增了几个有用的并发工具类,一样可以保证线程安全。其中,Semaphore类代表信号量,可以控制同时访问特定资源的线程数量CountDownLatch类则允许一个或多个线程等待其他线程完成操作CyclicBarrier可以让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才会继续运行。
(来自https://www.nowcoder.com/exam/interview/75826387/test?paperId=50270024)
### CountDownLatch