高级并发工具
并发集合
之前发过
线程池
Executors类的工厂方法
Java中的Executors类提供了一些静态工厂方法,用于创建常见的线程池类型:
-
newFixedThreadPool(int nThreads):
- 创建一个固定大小的线程池。
- 当线程数达到最大值时,新的任务将在队列中等待。
- 适用于需要限制线程数量以满足资源管理的场景。
-
newCachedThreadPool():
- 创建一个可以根据需要创建新线程的线程池。
- 如果线程池中有可用线程,将重用它们;否则,将创建新线程。
- 适用于执行许多短期异步任务的场景。
-
newSingleThreadExecutor():
- 创建一个单线程的线程池。
- 这个线程池保证所有任务在同一个线程中顺序执行。
- 适用于需要保证任务顺序执行的场景。
-
newScheduledThreadPool(int corePoolSize):
- 创建一个支持定时和周期性任务执行的线程池。
- 适用于需要在给定延迟后运行任务或定期执行任务的场景。
使用场景和示例代码
1. 固定大小的线程池(newFixedThreadPool)
适用场景:需要限制并发线程数量以控制资源使用。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
final int index = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is handling task " + index);
});
}
executor.shutdown();
}
}
2. 可缓存的线程池(newCachedThreadPool)
适用场景:执行很多短期异步任务时,可以提高性能。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
final int index = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is handling task " + index);
});
}
executor.shutdown();
}
}
3. 单线程的线程池(newSingleThreadExecutor)
适用场景:需要保证任务按顺序执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
final int index = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is handling task " + index);
});
}
executor.shutdown();
}
}
4. 定时线程池(newScheduledThreadPool)
适用场景:需要定时或周期性执行任务。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
executor.schedule(() -> {
System.out.println("Task executed after 3 seconds");
}, 3, TimeUnit.SECONDS);
executor.scheduleAtFixedRate(() -> {
System.out.println("Periodic task executed every 2 seconds");
}, 1, 2, TimeUnit.SECONDS);
executor.scheduleWithFixedDelay(() -> {
System.out.println("Fixed delay task executed after previous task completes");
}, 1, 2, TimeUnit.SECONDS);
}
}
池化技术
学习思想
池化技术是一种提高系统性能和资源利用效率的常用方法,在Java高级开发中尤为重要。以下是池化技术相关的知识点及经典使用场景:
-
基本概念:
- 池化:将一组可以重复使用的资源(如线程、连接、对象等)维护在一个池中,避免频繁创建和销毁资源,提升性能和效率。
- 资源池:管理和维护可复用资源的容器,如线程池、连接池、对象池等。
-
常见类型:
- 线程池(Thread Pool):复用一组线程来执行多个任务,减少线程创建和销毁的开销。
- 连接池(Connection Pool):维护数据库连接的复用,避免频繁创建和关闭数据库连接。
- 对象池(Object Pool):维护可复用对象的池,减少对象创建和销毁的开销。
-
优点:
- 性能提升:减少资源创建和销毁的频率,降低系统开销。
- 资源复用:高效管理和复用资源,提高资源利用率。
- 管理方便:统一管理资源的分配和回收,提升系统的可维护性。
-
实现机制:
- 资源初始化:在池创建时预先初始化一定数量的资源。
- 资源分配:从池中获取资源供客户端使用。
- 资源回收:使用完毕后,将资源归还池中以备复用。
- 资源销毁:在资源不再需要时,释放其占用的系统资源。
经典使用场景
-
线程池:
- 场景:并发任务执行,例如处理大量请求、后台任务调度。
- 实现:使用
Executors类创建不同类型的线程池(如固定大小线程池、缓存线程池、单线程池)。 - 示例:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { executor.execute(() -> { System.out.println(Thread.currentThread().getName() + " is executing task."); }); } executor.shutdown(); } } -
数据库连接池:
- 场景:数据库操作频繁的应用,例如Web应用、企业级应用。
- 实现:使用数据库连接池(如HikariCP、C3P0、Druid)管理数据库连接。
- 示例:
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; public class ConnectionPoolExample { public static void main(String[] args) throws Exception { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb"); config.setUsername("user"); config.setPassword("password"); HikariDataSource dataSource = new HikariDataSource(config); try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM mytable")) { while (rs.next()) { System.out.println(rs.getString("column1")); } } } } -
对象池:
- 场景:高频次使用某些重资源对象,如大数据量处理中的对象重用。
- 实现:使用第三方库(如Apache Commons Pool)或自行实现对象池。
- 示例:
import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool; public class ObjectPoolExample { public static void main(String[] args) throws Exception { ObjectPool<MyObject> pool = new GenericObjectPool<>(new MyObjectFactory()); MyObject obj1 = pool.borrowObject(); // 使用对象 pool.returnObject(obj1); } } class MyObject { // 对象的具体实现 } class MyObjectFactory extends BasePooledObjectFactory<MyObject> { @Override public MyObject create() throws Exception { return new MyObject(); } @Override public PooledObject<MyObject> wrap(MyObject obj) { return new DefaultPooledObject<>(obj); } }
在Java高级开发面试中,深入了解ThreadPoolExecutor的配置与参数是一个重要的知识点。ThreadPoolExecutor是Java并发包(java.util.concurrent)中用于创建和管理线程池的核心类。它允许开发者灵活配置线程池的行为,以满足不同的应用需求。下面是ThreadPoolExecutor的配置和参数的详细说明:
ThreadPoolExecutor配置与参数
构造方法
ThreadPoolExecutor提供多个构造方法,最常用的是以下这个:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数解释
-
corePoolSize(核心线程池大小):
- 线程池中保持的线程数量,即使它们处于空闲状态。
- 这些线程用于执行提交的任务,除非设置了
allowCoreThreadTimeOut。
-
maximumPoolSize(最大线程池大小):
- 线程池中允许的最大线程数量。
- 当队列满时,如果线程数少于最大线程数,会创建新的线程。
-
keepAliveTime(线程空闲保持时间):
- 当线程数超过
corePoolSize时,空闲线程的存活时间。 - 超过这个时间的空闲线程将被终止。
- 当线程数超过
-
unit(时间单位):
keepAliveTime参数的时间单位。常用的时间单位包括TimeUnit.SECONDS、TimeUnit.MILLISECONDS等。
-
workQueue(工作队列):
- 用于存放等待执行任务的队列。常用的队列实现包括:
SynchronousQueue:不存储任务,直接将任务交给线程执行。LinkedBlockingQueue:一个基于链表的有界阻塞队列,按FIFO顺序保存任务。ArrayBlockingQueue:一个基于数组的有界阻塞队列。PriorityBlockingQueue:一个支持优先级的无界阻塞队列。
- 用于存放等待执行任务的队列。常用的队列实现包括:
-
threadFactory(线程工厂):
- 用于创建新线程。可以通过实现
ThreadFactory接口来自定义线程工厂,通常用于设置线程名称、是否为守护线程等属性。
- 用于创建新线程。可以通过实现
-
handler(拒绝策略):
- 当线程池和队列都满时,执行被拒绝任务的处理程序。常用的拒绝策略包括:
AbortPolicy(默认):抛出RejectedExecutionException异常。CallerRunsPolicy:调用运行该任务的线程来执行任务。DiscardPolicy:丢弃任务,不抛出异常。DiscardOldestPolicy:丢弃队列中最旧的任务,然后尝试重新提交任务。
- 当线程池和队列都满时,执行被拒绝任务的处理程序。常用的拒绝策略包括:
示例代码
下面是一个ThreadPoolExecutor的配置示例,展示了如何创建和使用一个线程池:
import java.util.concurrent.*;
public class ThreadPoolExecutorExample {
public static void main(String[] args) {
int corePoolSize = 5;
int maximumPoolSize = 10;
long keepAliveTime = 60;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
for (int i = 0; i < 50; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is executing task.");
try {
Thread.sleep(2000); // Simulate task execution
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
重要概念
-
线程池大小设置:
corePoolSize和maximumPoolSize应根据任务的特性和系统资源合理设置。- 可以通过CPU密集型任务(线程数设置为
CPU核数+1)和IO密集型任务(线程数可以设置为更大的值)来优化线程池大小。
-
任务队列选择:
- 任务队列的选择对线程池行为有重要影响,例如
LinkedBlockingQueue适合大多数常规任务,SynchronousQueue适合高并发短任务。
- 任务队列的选择对线程池行为有重要影响,例如
-
拒绝策略选择:
- 需要根据实际应用场景选择合适的拒绝策略,避免系统过载时任务丢失或异常抛出。
-
线程工厂自定义:
- 通过自定义线程工厂,可以设置线程名称、是否为守护线程、优先级等属性,有助于调试和监控。
学习ThreadPoolExecutor设计
ThreadPoolExecutor 构造函数的关键因素和设计考量:
关键因素和设计考量
-
线程池大小配置:
- 核心线程数 (
corePoolSize):线程池中保持活跃的线程数量,即使它们是空闲的。 - 最大线程数 (
maximumPoolSize):线程池中允许的最大线程数量。当工作队列已满时,若核心线程已全部占用且未达到最大线程数,则创建新的线程来处理任务。 - 合理设置:需要根据任务类型(CPU密集型、I/O密集型)和系统资源进行合理设置。比如,CPU密集型任务通常设置为CPU核心数+1,而I/O密集型任务则可以设置更多。
- 核心线程数 (
-
线程存活时间 (
keepAliveTime) 和时间单位 (unit):- 当线程数超过核心线程数时,超过此存活时间的空闲线程将被终止。该时间单位通常为秒、毫秒等。
- 长时间空闲处理:设置一个合适的存活时间,可以在任务高峰期过后释放不必要的资源,避免系统资源浪费。
-
工作队列 (
workQueue):- 用于存放等待执行任务的队列。常见的有:
- 无界队列 (
LinkedBlockingQueue):没有大小限制,但可能导致任务积压,内存耗尽。 - 有界队列 (
ArrayBlockingQueue):有大小限制,适用于控制资源的应用场景。 - 优先级队列 (
PriorityBlockingQueue):按任务优先级执行。 - 同步队列 (
SynchronousQueue):每个插入操作都要等待一个删除操作,适用于高并发短任务。
- 无界队列 (
- 选择合适的队列:需要根据应用场景选择合适的工作队列,以平衡性能和资源使用。
- 用于存放等待执行任务的队列。常见的有:
-
线程工厂 (
threadFactory):- 用于创建新线程。可以通过实现
ThreadFactory接口来自定义线程工厂,通常用于设置线程名称、优先级、是否为守护线程等属性。 - 自定义线程工厂:有助于调试、监控和管理线程。
- 用于创建新线程。可以通过实现
-
拒绝策略 (
handler):- 当线程池和工作队列都满时,处理新任务的策略。常见的拒绝策略包括:
- AbortPolicy:直接抛出
RejectedExecutionException异常。 - CallerRunsPolicy:由调用线程处理该任务。
- DiscardPolicy:直接丢弃任务,不抛出异常。
- DiscardOldestPolicy:丢弃队列中最旧的任务,然后尝试重新提交。
- AbortPolicy:直接抛出
- 合理的拒绝策略:根据实际需求选择合适的拒绝策略,以避免系统过载和任务丢失。
- 当线程池和工作队列都满时,处理新任务的策略。常见的拒绝策略包括:
-
线程安全:
- 确保线程池的内部操作是线程安全的,避免线程之间的竞争和数据不一致问题。
- 锁机制与并发控制:使用合适的锁机制(如
ReentrantLock、synchronized)和并发控制技术(如CAS操作、条件变量)来保证线程安全。
示例设计
以下是一个简化版的线程池实现示例,包含上述设计因素:
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CustomThreadPoolExecutor {
private final int corePoolSize;
private final int maximumPoolSize;
private final long keepAliveTime;
private final TimeUnit unit;
private final BlockingQueue<Runnable> workQueue;
private final ThreadFactory threadFactory;
private final RejectedExecutionHandler handler;
private final AtomicInteger activeThreads = new AtomicInteger(0);
public CustomThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.keepAliveTime = keepAliveTime;
this.unit = unit;
this.workQueue = workQueue;
this.threadFactory = threadFactory;
this.handler = handler;
}
public void execute(Runnable command) {
if (command == null) throw new NullPointerException();
if (activeThreads.get() < corePoolSize) {
if (addWorker(command, true)) return;
}
if (workQueue.offer(command)) {
// Task accepted by queue
} else if (activeThreads.get() < maximumPoolSize && addWorker(command, false)) {
// Task accepted by new worker
} else {
handler.rejectedExecution(command, this);
}
}
private boolean addWorker(Runnable firstTask, boolean core) {
// Simplified worker creation and start
Thread thread = threadFactory.newThread(() -> {
try {
while (firstTask != null || (firstTask = workQueue.poll(keepAliveTime, unit)) != null) {
try {
firstTask.run();
} finally {
firstTask = null;
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
activeThreads.decrementAndGet();
}
});
if (thread != null) {
activeThreads.incrementAndGet();
thread.start();
return true;
}
return false;
}
public void shutdown() {
// Shutdown logic
}
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
// Termination logic
return true;
}
// Additional methods...
}
其他语言的实现
在其他编程语言中,如Python、C++、JavaScript等,可以基于上述设计原则和关键因素实现类似的线程池。以下是一些实现要点:
- 线程池大小:配置核心线程数和最大线程数。
- 线程存活时间:配置空闲线程的存活时间。
- 工作队列:选择适合的队列类型。
- 线程工厂:提供自定义线程创建方式。
- 拒绝策略:实现任务拒绝处理逻辑。
- 线程安全:确保多线程操作的安全性。
ThreadLocal
ThreadLocal 是 Java 中提供的一种机制,用于在同一个线程中存储和访问全局变量。每个线程可以通过 ThreadLocal 维护自己独立的变量副本,从而避免了多线程环境下的线程安全问题。
作用和使用场景
- 线程独立变量:每个线程都有自己的变量副本,互不干扰。
- 线程安全:避免了使用同步机制,保证线程安全。
- 上下文信息传递:适用于传递与线程关联的上下文信息,如用户会话、事务等。
常见使用场景
- 用户会话信息:在Web应用中,存储每个用户的会话信息。
- 数据库连接:在数据库操作中,存储每个线程独立的数据库连接。
- 事务管理:在事务处理时,存储与当前线程关联的事务信息。
基本使用方法
以下是 ThreadLocal 的基本使用示例:
public class ThreadLocalExample {
// 创建一个 ThreadLocal 变量
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Runnable task = () -> {
// 获取当前线程的变量值
int value = threadLocal.get();
System.out.println(Thread.currentThread().getName() + " initial value: " + value);
// 设置当前线程的变量值
threadLocal.set(value + 1);
System.out.println(Thread.currentThread().getName() + " updated value: " + threadLocal.get());
};
// 启动多个线程
Thread thread1 = new Thread(task, "Thread-1");
Thread thread2 = new Thread(task, "Thread-2");
thread1.start();
thread2.start();
}
}
在上述代码中,每个线程都有自己的 ThreadLocal 变量副本,不会相互影响。
深入理解
-
内存泄漏问题:
- 强引用与弱引用:
ThreadLocal采用弱引用存储键值,当ThreadLocal实例没有强引用时,可以被垃圾回收。但是,ThreadLocal的值是强引用,可能导致内存泄漏。 - 清理机制:在每次调用
get(),set(), 或remove()方法时,ThreadLocal会尝试清理已经被回收的键对应的值。 - 最佳实践:使用
remove()方法在任务完成后显式地删除变量,避免内存泄漏。
public class ThreadLocalExample { private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0); public static void main(String[] args) { try { int value = threadLocal.get(); System.out.println("Initial value: " + value); threadLocal.set(value + 1); System.out.println("Updated value: " + threadLocal.get()); } finally { threadLocal.remove(); // 避免内存泄漏 } } } - 强引用与弱引用:
-
实现原理:
- 内部结构:
ThreadLocal的变量保存在当前线程的ThreadLocalMap中,该映射是当前线程私有的。 - ThreadLocalMap:内部使用
Entry类存储键值对,Entry继承自WeakReference,键是弱引用,值是强引用。
- 内部结构:
-
适用场景:
- 短生命周期变量:适用于存储生命周期较短的变量,如临时的上下文信息、用户会话数据等。
- 线程隔离的数据:需要在线程之间隔离的数据,如用户请求的处理、数据处理上下文等。
面试回答示例
在面试中,关于 ThreadLocal 的问题可以这样回答:
ThreadLocal 是 Java 中提供的一种机制,用于在同一个线程中存储和访问全局变量。每个线程都拥有自己独立的变量副本,避免了多线程环境下的线程安全问题。ThreadLocal 的典型使用场景包括用户会话信息存储、数据库连接管理和事务处理等。在使用 ThreadLocal 时,需要注意内存泄漏问题,最好在任务完成后显式地调用 remove() 方法进行清理。ThreadLocal 通过在每个线程中维护一个私有的 ThreadLocalMap 实现,其键是弱引用,可以被垃圾回收,但其值是强引用,可能导致内存泄漏,因此需要谨慎使用。
ThreadLocal in Tomcat
Tomcat 是一个多线程的 Java Web 容器,ThreadLocal 在其中用于保存线程私有的数据,避免了同步开销,并且能够在同一请求的不同处理阶段共享数据。以下是一些典型的应用场景:
1. 数据库连接管理
在 Tomcat 中,一个常见的应用场景是通过 ThreadLocal 来管理数据库连接。每个请求线程会从连接池中获取一个数据库连接,并存储在 ThreadLocal 中,这样在请求处理的不同阶段都可以方便地访问到该连接。
public class DatabaseConnectionManager {
private static ThreadLocal<Connection> connectionHolder = ThreadLocal.withInitial(() -> {
try {
// 假设DataSource是已经配置好的数据源
return DataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
public static Connection getConnection() {
return connectionHolder.get();
}
public static void closeConnection() {
try {
connectionHolder.get().close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
connectionHolder.remove();
}
}
}
2. 用户会话管理
在 Tomcat 中,通过 ThreadLocal 可以方便地管理每个线程独立的用户会话信息。这种方式可以避免将会话信息传递给每个方法,简化代码。
public class UserSession {
private static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
public static void setUser(User user) {
userThreadLocal.set(user);
}
public static User getUser() {
return userThreadLocal.get();
}
public static void clear() {
userThreadLocal.remove();
}
}
3. 日志记录
在处理请求时,通过 ThreadLocal 可以将一些上下文信息(如请求ID、用户ID等)保存起来,方便日志记录时使用。
public class LoggingContext {
private static ThreadLocal<String> requestId = ThreadLocal.withInitial(() -> UUID.randomUUID().toString());
public static String getRequestId() {
return requestId.get();
}
public static void clear() {
requestId.remove();
}
}
4. 事务管理
在分布式事务处理中,可以使用 ThreadLocal 存储当前线程的事务状态,避免事务状态在方法调用之间传递。
public class TransactionManager {
private static ThreadLocal<Transaction> transactionHolder = new ThreadLocal<>();
public static void startTransaction() {
Transaction tx = new Transaction();
transactionHolder.set(tx);
}
public static Transaction getCurrentTransaction() {
return transactionHolder.get();
}
public static void endTransaction() {
Transaction tx = transactionHolder.get();
if (tx != null) {
tx.commit();
}
transactionHolder.remove();
}
}
5. 内存泄漏问题
在 Tomcat 中使用 ThreadLocal 时要特别注意内存泄漏问题。Tomcat 的线程池会重用线程,如果没有清理 ThreadLocal 中的变量,会导致数据在不同请求之间泄漏,甚至导致内存泄漏。因此,在每次请求处理完毕后,应该显式调用 remove() 方法清理 ThreadLocal 变量。
为什么使用到ThreadLocal的时候都是静态值,静态变量是全局唯一,不会在多线程环境出问题吗?
ThreadLocal是一个用于在多个线程之间提供独立变量副本的类。每个线程可以独立地改变其副本,而不影响其他线程的副本。这使得ThreadLocal特别适合在多线程环境中使用,而不需要显式的同步。静态变量与
ThreadLocal
在 Java 中,静态变量是所有实例共享的。但是,ThreadLocal提供了一种机制,使得每个线程都可以拥有自己的独立变量副本,而不是共享静态变量。这就解释了为什么ThreadLocal的实例通常声明为静态变量的原因。
public class MyClass {
private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);
public void someMethod() {
int value = threadLocalValue.get();
// Do something with value
threadLocalValue.set(value + 1);
}
}
为什么使用静态变量
- 方便访问:将
ThreadLocal声明为静态变量,使得它可以在类的不同方法中方便地访问,而不需要每次都传递ThreadLocal实例。- 全局唯一:静态变量在类加载时初始化,并且在整个应用程序的生命周期内都只有一个实例。通过使用静态
ThreadLocal变量,可以确保每个线程在整个应用程序中都使用相同的ThreadLocal实例,避免了在不同类或方法中重复创建ThreadLocal实例的麻烦。静态
ThreadLocal变量不会共享的问题
虽然ThreadLocal变量本身是静态的,但它所管理的值(即每个线程的副本)并不是静态的。每个线程都有一个独立的副本,这些副本之间是完全隔离的。因此,不同线程对ThreadLocal变量的访问是线程安全的,每个线程只能看到自己独立的副本。
ThreadLocal get源码
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //每个线程都有这个变量
}
Future和FutureTask
Future 接口
-
概述
Future接口是 Java 中用于表示异步计算结果的接口。它提供了一种获取异步计算结果的方式,允许检查计算是否完成、等待计算完成以及获取计算的结果。 -
基本方法
boolean cancel(boolean mayInterruptIfRunning): 尝试取消任务的执行。boolean isCancelled(): 如果任务在取消前完成,则返回 true。boolean isDone(): 如果任务已完成,则返回 true。V get() throws InterruptedException, ExecutionException: 获取计算的结果,如果计算尚未完成,则阻塞当前线程直到计算完成。
-
示例
ExecutorService executor = Executors.newFixedThreadPool(1); // 提交一个异步任务,并获得 Future 对象 Future<String> futureResult = executor.submit(() -> { Thread.sleep(2000); // 模拟耗时操作 return "Hello, Future!"; }); // 检查任务是否完成 if (!futureResult.isDone()) { System.out.println("Task is still running..."); } // 等待任务完成,并获取结果 try { String result = futureResult.get(); // 阻塞直到任务完成 System.out.println("Task result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown();
FutureTask 类
-
概述
FutureTask类实现了RunnableFuture接口,是Future接口的一个实现,同时也是Runnable接口的实现。它允许将Future与Runnable结合使用,用于异步执行任务和获取结果。 -
特性
- 可以由
Executor执行FutureTask。 - 可以手动创建和执行。
- 可以用作
Runnable传递给Thread对象执行。
- 可以由
-
示例
// 创建一个 FutureTask FutureTask<String> futureTask = new FutureTask<>(() -> { Thread.sleep(3000); // 模拟耗时操作 return "Hello, FutureTask!"; }); // 使用 Executor 执行 FutureTask ExecutorService executor = Executors.newFixedThreadPool(1); executor.execute(futureTask); // 获取任务结果 try { String result = futureTask.get(); // 阻塞直到任务完成 System.out.println("FutureTask result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown();
典型面试问题和回答示例
-
什么是
Future接口?Future接口是 Java 中用于异步计算结果的接口,它提供了一种非阻塞获取任务执行结果的机制。 -
FutureTask的作用是什么?FutureTask类实现了Future接口和Runnable接口,允许将Future与Runnable结合使用,用于异步执行任务和获取结果。 -
如何使用
Future和FutureTask执行异步任务?可以通过
ExecutorService提交Callable或Runnable对象创建Future或FutureTask实例,然后通过get()方法获取计算结果。
CompletableFuture
CompletableFuture 是 Java 8 引入的一个类,它提供了异步编程的强大功能,允许你以非阻塞的方式执行任务并处理它们的结果。
CompletableFuture是Future的实现类
-
创建和完成
CompletableFutureCompletableFuture可以通过静态工厂方法创建,例如CompletableFuture.supplyAsync()和CompletableFuture.runAsync()。- 可以手动完成
CompletableFuture,使用complete方法。
-
链式调用和组合
thenApply:在前一个任务完成后,使用其结果执行一个函数。thenAccept:在前一个任务完成后,使用其结果执行一个消费者(不返回结果)。thenRun:在前一个任务完成后,执行一个无参的 Runnable(不使用结果)。thenCombine:组合两个CompletableFuture的结果。thenCompose:将一个CompletableFuture的结果作为另一个异步操作的输入。
-
异常处理
exceptionally:处理异常并返回默认值。handle:处理正常结果和异常。whenComplete:在CompletableFuture完成时执行回调,无论是正常完成还是异常完成。
-
并行执行
allOf:等待多个CompletableFuture全部完成。anyOf:等待任意一个CompletableFuture完成。
使用示例
创建和完成 CompletableFuture
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 执行一些异步操作
return "Hello, World!";
});
future.thenAccept(result -> {
// 处理结果
System.out.println(result);
});
异常处理
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("Something went wrong!");
}
return "Success!";
});
future.exceptionally(ex -> {
// 处理异常
System.out.println("Error: " + ex.getMessage());
return "Default Value";
}).thenAccept(result -> {
// 处理结果
System.out.println(result);
});
链式调用和组合
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2);
combinedFuture.thenAccept(result -> {
// 输出 "Hello World"
System.out.println(result);
});
并行执行
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Task 3");
CompletableFuture<Void> allOf = CompletableFuture.allOf(future1, future2, future3);
allOf.thenRun(() -> {
// 所有任务完成后执行
System.out.println("All tasks completed.");
});
典型面试问题和回答示例
-
什么是
CompletableFuture?CompletableFuture是 Java 8 中引入的一个类,用于处理异步编程。它提供了非阻塞的方法来执行任务并处理它们的结果,使代码更简洁和可读。 -
如何处理
CompletableFuture的异常?可以使用
exceptionally方法来处理异常并提供默认值,或者使用handle方法来处理正常结果和异常。此外,可以使用whenComplete方法在CompletableFuture完成时执行回调,无论是否发生异常。 -
如何组合多个
CompletableFuture?可以使用
thenCombine方法组合两个CompletableFuture的结果,使用allOf方法等待多个CompletableFuture全部完成,或者使用anyOf方法等待任意一个CompletableFuture完成。
同步辅助类
下面的类来自包 java.util.concurrent
在 Java 的并发编程中,java.util.concurrent 包提供了一些强大的工具类来简化多线程开发。这些工具类可以帮助你协调多个线程的操作、控制线程间的通信和同步。下面是对 CountDownLatch、CyclicBarrier、Semaphore 和 BlockingQueue 等类的详细介绍:
1. CountDownLatch
CountDownLatch 是一种同步辅助类,用于使一个或多个线程等待,直到其他线程完成一组操作。其核心思想是有一个计数器,线程可以在这个计数器上等待,而其他线程可以减少这个计数器的值。
主要方法:
countDown(): 递减计数器的值。await(): 使当前线程等待,直到计数器的值变为零。
使用示例:
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 执行任务
} finally {
latch.countDown();
}
}).start();
}
latch.await(); // 等待所有任务完成
System.out.println("所有任务已完成");
2. CyclicBarrier
CyclicBarrier 是一种同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点。它与 CountDownLatch 不同的是,CyclicBarrier 在所有线程都到达屏障点后可以重新使用。
主要方法:
await(): 使当前线程在屏障点等待,直到所有线程都到达屏障点。
使用示例:
int parties = 3;
CyclicBarrier barrier = new CyclicBarrier(parties, () -> {
System.out.println("所有线程到达屏障点");
});
for (int i = 0; i < parties; i++) {
new Thread(() -> {
try {
// 执行任务
barrier.await();
// 执行屏障后的任务
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
3. Semaphore
Semaphore 是一种计数信号量,用于控制同时访问特定资源的线程数量。它通过允许一定数量的许可来限制并发访问。
主要方法:
acquire(): 获取一个许可,如果没有可用的许可,则阻塞。release(): 释放一个许可,增加可用许可的数量。
使用示例:
int permits = 5;
Semaphore semaphore = new Semaphore(permits);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();
// 执行需要控制并发访问的任务
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
4. BlockingQueue
BlockingQueue 是一种支持线程安全操作的队列,它在插入和移除元素时可以自动阻塞或唤醒线程。常用实现类有 ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue 等。
主要方法:
put(E e): 将元素插入队列,如果队列已满则阻塞。take(): 从队列中取出一个元素,如果队列为空则阻塞。
使用示例:
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);
new Thread(() -> {
try {
queue.put(1);
// 插入更多元素
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
Integer value = queue.take();
System.out.println("取出元素: " + value);
// 取出更多元素
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
Exchanger
作用: Exchanger类提供了一个同步点,允许两个线程在同一个时间点上交换数据。每个线程在调用exchange()方法时会被阻塞,直到另一个线程也调用了同样的方法,然后两者交换数据并继续执行。
主要方法:
exchange(V x): 在此方法上调用的线程将自己的数据x与另一个线程交换,如果没有另一个线程调用exchange,则会一直阻塞。exchange(V x, long timeout, TimeUnit unit): 允许设置超时时间,如果在指定时间内没有另一个线程到达交换点,则会抛出超时异常。
应用场景: 可用于遗传算法、流水线设计、以及其他需要线程间数据交换的场景。
Phaser
作用: Phaser类提供了更灵活的线程同步方式,支持多阶段并发任务的控制和同步。
主要方法:
register(): 向Phaser注册一个参与者(线程)。arrive(): 表示一个线程已经到达了同步点,会阻塞直到所有注册的参与者都到达。arriveAndAwaitAdvance(): 表示一个线程到达同步点并等待其他参与者到达。arriveAndDeregister(): 表示一个线程到达同步点后注销自己,不再参与后续的同步操作。awaitAdvance(int phase): 在指定阶段等待,直到Phaser进入下一个阶段。
应用场景: 适合需要分阶段并发控制的复杂算法和流程,如分布式任务协调、多阶段数据处理等
总结
- CountDownLatch: 用于一个或多个线程等待其他线程完成操作。
- CyclicBarrier: 用于一组线程互相等待,直到所有线程到达屏障点。
- Semaphore: 用于控制同时访问资源的线程数量。
- BlockingQueue: 用于线程安全地插入和移除元素,支持阻塞操作。
- Exchanger: 用于允许两个线程在同一时间点交换数据
- Phaser: 用于多阶段并发任务控制和同步

1266

被折叠的 条评论
为什么被折叠?



