BlockingQueue 是 Java 中的一个接口,它代表了一个线程安全的队列,不仅可以由多个线程并发访问,还添加了等待/通知机制,以便在队列为空时阻塞获取元素的线程,直到队列变得可用,或者在队列满时阻塞插入元素的线程,直到队列变得可用。
最常见的"生产者-消费者"问题中,队列通常被视作线程间的数据容器,生产者将“生产”出来的数据放入数据容器,消费者从“数据容器”中获取数据,这样,生产者线程和消费者线程就解耦了,各自只需要专注自己的业务即可。
BlockingQueue 是 Java 中的一个接口,它代表了一个线程安全的队列,不仅可以由多个线程并发访问,还添加了等待/通知机制,以便在队列为空时阻塞获取元素的线程,直到队列变得可用,或者在队列满时阻塞插入元素的线程,直到队列变得可用。
最常见的"生产者-消费者"问题中,队列通常被视作线程间的数据容器,生产者将“生产”出来的数据放入数据容器,消费者从“数据容器”中获取数据,这样,生产者线程和消费者线程就解耦了,各自只需要专注自己的业务即可。
ConcurrentHashMap 是 Java 并发包 (java.util.concurrent) 中的一种线程安全的哈希表实现。
HashMap 在多线程环境下扩容会出现 CPU 接近 100% 的情况,因为 HashMap 并不是线程安全的,我们可以通过 Collections 的Map<K,V> synchronizedMap(Map<K,V> m)
将 HashMap 包装成一个线程安全的 map。
ConcurrentLinkedQueue 是 java.util.concurrent
(JUC) 包下的一个线程安全的队列实现。基于非阻塞算法(Michael-Scott 非阻塞算法的一种变体),这意味着 ConcurrentLinkedQueue 不再使用传统的锁机制来保护数据安全,而是依靠底层原子的操作(如 CAS)来实现。
Michael-Scott 由 Maged M. Michael 和 Michael L. Scott 在 1996 年提出,在这种算法中,一个线程的失败或挂起不会导致其他线程也失败或挂起。
学过 ArrayList 的小伙伴应该记得,ArrayList 是一个线程不安全的容器,如果在多线程环境下使用,需要手动加锁,或者使用 Collections.synchronizedList()
方法将其转换为线程安全的容器。
否则,将会出现 ConcurrentModificationException 异常。
JDK 中提供了一些并发编程中常用的通信工具类以供我们开发者使用,比如说 CountDownLatch,Semaphore,Exchanger,CyclicBarrier,Phaser。
它们都在 JUC 包下。先总体概括一下都有哪些工具类,它们有什么作用,然后再分别介绍它们的主要使用方法和原理。
类 | 作用 |
---|---|
Semaphore | 限制线程的数量 |
Exchanger | 两个线程交换数据 |
CountDownLatch | 线程等待直到计数器减为 0 时开始工作 |
CyclicBarrier | 作用跟 CountDownLatch 类似,但是可以重复使用 |
Phaser | 增强的 CyclicBarrier |
LockSupprot 用来阻塞和唤醒线程,底层实现依赖于 Unsafe 类(后面会细讲)。
该类包含一组用于阻塞和唤醒线程的静态方法,这些方法主要是围绕 park 和 unpark 展开,话不多说,直接来看一个简单的例子吧。
public class LockSupportDemo1 {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
// 创建一个线程从1数到1000
Thread counterThread = new Thread(() -> {
for (int i = 1; i <= 1000; i++) {
System.out.println(i);
if (i == 500) {
// 当数到500时,唤醒主线程
LockSupport.unpark(mainThread);
}
}
});
counterThread.start();
// 主线程调用park
LockSupport.park();
System.out.println("Main thread was unparked.");
}
}
ReentrantReadWriteLock 是 Java 的一种读写锁,它允许多个读线程同时访问,但只允许一个写线程访问(会阻塞所有的读写线程)。这种锁的设计可以提高性能,特别是在读操作的数量远远超过写操作的情况下。
在并发场景中,为了解决线程安全问题,我们通常会使用关键字 synchronized 或者 JUC 包中实现了 Lock 接口的 ReentrantLock。但它们都是独占式获取锁,也就是在同一时刻只有一个线程能够获取锁。
定时任务 ScheduledThreadPoolExecutor
类有两个用途:指定时间延迟后执行任务;周期性重复执行任务。
JDK 1.5 之前,主要使用Timer
类来完成定时任务,但是Timer
有以下缺陷:
是 Java 中提供的一种用于实现线程局部变量的工具类。它允许每个线程都拥有自己的独立副本,从而实现线程隔离,用于解决多线程中共享对象的线程安全问题。
通常,我们会使用 synchronzed 关键字 或者 lock 来控制线程对临界区资源的同步顺序,但这种加锁的方式会让未获取到锁的线程进行阻塞,很显然,这种方式的时间效率不会特别高。
前面我们在讲 CAS 和原子操作 atomic 类的时候,都讲到了 Unsafe。
Unsafe 是 Java 中一个非常特殊的类,它为 Java 提供了一种底层、"不安全"的机制来直接访问和操作内存、线程和对象。正如其名字所暗示的,Unsafe 提供了许多不安全的操作,因此它的使用应该非常小心,并限于那些确实需要使用这些底层操作的场景。