Java多线程学习——JDK工具篇

线程池

线程池的原理是通过预先创建一定数量的线程,将它们放入一个队列中,当有任务需要执行时,从队列中取出一个空闲线程来执行任务。当任务执行完毕后,线程会返回队列中等待下一个任务。这样可以避免频繁地创建和销毁线程,提高系统性能。

线程池的主要组成部分包括:

  1. 核心线程数(corePoolSize):线程池中始终保持活跃的线程数量。
  2. 最大线程数(maximumPoolSize):线程池允许的最大线程数量。
  3. 空闲线程存活时间(keepAliveTime):非核心线程闲置的最长时间,超过这个时间的线程将被终止。
  4. 时间单位(unit):keepAliveTime的时间单位。
  5. 阻塞队列(workQueue):用于存放等待执行的任务。
  6. 线程工厂(threadFactory):用于创建新线程的工厂。
  7. 拒绝策略(handler):当线程池无法处理新任务时采取的策略。

线程池的工作流程如下:
8. 当提交一个新任务时,线程池首先检查当前线程数是否小于核心线程数,如果是,则创建一个新的线程来执行任务;否则,将任务添加到阻塞队列中。
9. 如果阻塞队列已满且当前线程数小于最大线程数,则创建一个新的线程来执行任务;否则,根据拒绝策略处理无法执行的任务。
10. 当一个线程完成任务后,它会返回到阻塞队列中等待新的任务。如果超过了空闲线程存活时间,该线程将被终止。

ThreadPoolExecutor的策略

线程池的状态主要有以下几个:

  1. RUNNING:线程池处于运行状态,可以接受新任务并处理阻塞队列中的任务。
  2. SHUTDOWN:线程池不再接受新任务,但会继续处理阻塞队列中的任务,直到队列为空。
  3. STOP:线程池不再接受新任务,中断所有正在执行的线程,并清空阻塞队列中的任务。
  4. TIDYING:线程池中的任务已经全部执行完毕,线程池正在整理和清理资源。
  5. TERMINATED:线程池已经完全终止,所有的资源已经被释放。

线程池的状态转换过程如下:

  1. 初始状态:创建线程池后,线程池处于RUNNING状态。
  2. 调用shutdown()方法:线程池进入SHUTDOWN状态,不再接受新任务,但会继续处理阻塞队列中的任务。
  3. 阻塞队列为空且线程池中没有活跃线程:线程池进入TIDYING状态,开始进行资源的清理工作。
  4. terminated()方法执行完成:线程池进入TERMINATED状态,所有资源已被释放。

JDK定义的四种线程池

四种常见的线程池分别是:

  1. newCachedThreadPool:创建一个可缓存的线程池,它会根据需要创建新线程,但如果线程空闲时间超过60秒,则会被回收。这种类型的线程池适用于执行大量短期异步任务的场景。

  2. newFixedThreadPool:创建一个固定大小的线程池,所有线程都是核心线程。当线程池中的线程都在执行任务时,新的任务会等待队列中的任务完成后再执行。这种类型的线程池适用于执行长期运行的任务。

  3. newSingleThreadExecutor:创建一个只有一个线程的线程池,这个线程池保证所有任务按照提交顺序依次执行。这种类型的线程池适用于需要顺序执行任务的场景。

  4. newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。这种类型的线程池适用于需要定时或周期性执行任务的场景。

需要注意的是,《阿里开发手册》建议不要直接使用Executors类中的线程池,而是通过ThreadPoolExecutor的方式创建线程池,这样可以让我们更清楚地了解线程池的运行规则,避免资源耗尽的风险。

阻塞队列

操作方法

阻塞队列的操作方法如下:

插入方法:

  1. add(e):将元素e添加到阻塞队列中,如果队列已满,则抛出IllegalStateException异常。
  2. offer(e):将元素e添加到阻塞队列中,如果队列已满,则返回false。
  3. put(e):将元素e添加到阻塞队列中,如果队列已满,则一直阻塞直到有空间为止。
  4. offer(e, time, unit):将元素e添加到阻塞队列中,如果在指定的等待时间内队列仍然没有空间,则返回false。

移除方法:
5. remove():移除并返回阻塞队列的头部元素,如果队列为空,则抛出NoSuchElementException异常。
6. poll():移除并返回阻塞队列的头部元素,如果队列为空,则返回null。
7. take():移除并返回阻塞队列的头部元素,如果队列为空,则一直阻塞直到有元素为止。
8. poll(time, unit):移除并返回阻塞队列的头部元素,如果在指定的等待时间内队列仍然为空,则返回null。

检查方法:
9. element():返回阻塞队列的头部元素,但不移除它。如果队列为空,则抛出NoSuchElementException异常。
10. peek():返回阻塞队列的头部元素,但不移除它。如果队列为空,则返回null。

BlockingQueue的实现类

ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue、PriorityBlockingQueue和SynchronousQueue是Java中常用的阻塞队列实现类。它们的主要特点如下:

  1. ArrayBlockingQueue:由数组结构组成的有界阻塞队列,内部结构是数组,具有数组的特性。可以初始化队列大小,且一旦初始化不能改变。构造方法中的fair表示控制对象的内部锁是否采用公平锁,默认是非公平锁。

  2. LinkedBlockingQueue:由链表结构组成的有界阻塞队列,内部结构是链表,具有链表的特性。默认队列的大小是Integer.MAX_VALUE,也可以指定大小。此队列按照先进先出的原则对元素进行排序。

  3. DelayQueue:该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。注入其中的元素必须实现java.util.concurrent.Delayed接口。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

  4. PriorityBlockingQueue:基于优先级的无界阻塞队列,内部控制线程同步的锁采用的是公平锁。优先级的判断通过构造函数传入的Comparator对象来决定。

  5. SynchronousQueue:这个队列比较特殊,没有任何内部容量,甚至连一个队列的容量都没有。并且每个put必须等待一个take,反之亦然。

锁接口和类

锁的分类

锁的分类主要包括以下几种:

  1. 可重入锁和非可重入锁:

    • 可重入锁:支持一个线程对资源重复加锁,如synchronized关键字和ReentrantLock类。
    • 非可重入锁:如果一个线程已经持有锁,再次尝试获取锁时会导致阻塞或异常。
  2. 公平锁与非公平锁:

    • 公平锁:按照请求锁的顺序来获取锁,即先到先得,遵循FIFO原则。
    • 非公平锁:不保证请求锁的顺序,可能会导致线程饥饿现象,但效率较高。
  3. 读写锁和排它锁:

    • 排它锁:在同一时刻只允许一个线程进行访问,如synchronized和ReentrantLock。
    • 读写锁:允许多个读线程同时访问,但在写线程访问时,所有读线程和其他写线程都会被阻塞。Java中通过ReentrantReadWriteLock类实现。

根据实际需求选择合适的锁类型可以提高程序的性能和并发能力。

并发集合容器

ConcurrentMap接口继承了Map接口,并增加了四个方法:

  1. putIfAbsent:如果key不存在,则插入元素;如果key相同,则不替换原有的value值。
  2. remove:如果要删除的key-value与Map中原有的key-value对应不上,则不会删除该元素。
  3. replace(K, V, V):如果key-oldValue与Map中原有的key-value对应上,才进行替换操作。
  4. replace(K, V):如果key存在,则直接替换value,不对Map中原有的key-value进行比较。

ConcurrentHashMap类是基于散列表的Map,它提供了一种粒度更细的加锁机制,称为分段锁(Lock Striping),以实现更高的并发性能。在ConcurrentHashMap中,数据被分段,每个段都有自己的锁。这种结构允许多个线程同时访问不同段的数据,从而提高了并发性能。

ConcurrentNavigableMap接口继承了NavigableMap接口,提供了最接近匹配项的导航方法。ConcurrentSkipListMap是ConcurrentNavigableMap的主要实现类,底层使用跳表(SkipList)数据结构,并使用CAS来保证并发安全性。

对于并发Queue,JDK提供了ConcurrentLinkedDeque和ConcurrentLinkedQueue这两个线程安全的队列类,它们使用CAS来实现线程安全。

在并发Set方面,JDK提供了ConcurrentSkipListSet,它是一个线程安全的有序集合,底层使用ConcurrentSkipListMap实现。谷歌的Guava框架也实现了一个线程安全的ConcurrentHashSet。

CopyOnWrite容器是一种线程安全的并发容器,它在写操作时会复制整个容器,而不是在原容器上进行修改。这样可以在多线程环境下实现读写分离,提高读操作的性能。但是,这种容器的缺点是在写操作时需要复制整个容器,可能导致内存压力较大和Full GC频繁。

CopyOnWriteArrayList是CopyOnWrite容器的一个实现,它提供了add、remove等方法。这些方法在执行时会先复制原容器,然后在新副本上进行写操作,最后将原容器引用指向新副本。在这个过程中,需要加锁以保证线程安全。

CopyOnWriteMap是另一个CopyOnWrite容器的实现,它实现了Map接口。它的put和putAll方法也是通过复制原容器并在新副本上进行写操作来实现的。同样,这些方法也需要加锁以保证线程安全。

在实际业务场景中,可以使用CopyOnWriteMap来存储一些不需要实时更新的数据,例如黑名单、配置信息等。由于CopyOnWriteMap在写操作时会复制整个容器,所以适用于读操作远多于写操作的场景。但是,如果需要实时更新数据,建议使用其他线程安全的并发容器,如ConcurrentHashMap。

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吴代庄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值