并发性是现代软件开发的基石,它使应用程序能够同时执行多个任务并高效利用系统资源。Java 是一种多功能且功能强大的编程语言,它已经发展到包含强大的并发支持,使开发人员能够创建高性能、可扩展的应用程序。本文深入介绍了 Java 中的高级并发模式和最佳实践,使开发人员能够掌握应对复杂并发挑战和优化其应用程序的知识。
1. Java 并发基础知识
基本并发概念
Java 中的并发性首先要了解基本构建块:线程和 Runnable 接口。线程是一个轻量级进程,可以与其他线程并发执行代码。Runnable 接口提供了一种定义可由线程执行的任务的方法。
public class BasicRunnable implements Runnable {
@Override
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String[] args) {
Thread thread = new Thread(new BasicRunnable());
thread.start();
}
}
在此示例中,创建并启动了一个新线程,执行 BasicRunnable 类的 run 方法。
并发编程中的挑战
并发编程带来了一些挑战,开发人员必须解决这些挑战才能确保正确性和性能:
- 竞争条件:当多个线程同时访问共享数据时发生,导致不可预测的结果。
- 死锁:当两个或多个线程无限期地等待对方释放资源时发生。
- 线程饥饿:当线程永久被拒绝访问资源,从而阻止其取得进展时发生。
了解这些挑战对于开发可靠的并发应用程序至关重要。
2. 高级并发构造
锁和信号量
Java 提供了诸如锁和信号量之类的高级并发结构,以便比 synchronized 关键字更精确地管理对共享资源的访问。
- ReentrantLock:提供对锁定机制的更多控制,包括中断和超时的能力。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final Lock lock = new ReentrantLock();
private int counter = 0;
public void increment() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
}
在这个例子中,ReentrantLock确保increment方法可以被多个线程安全地访问。
- 信号量:用于控制对固定数量资源的访问。
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private final Semaphore semaphore = new Semaphore ( 3 ); // 允许 3 个权限
public void accessResource () {
try {
semaphore.acquire();
// 访问资源
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
}
}
这里,信号量用于管理对资源的访问,只允许三个线程同时访问它。
真实示例:管理数据库连接
在 Web 应用程序中,可以使用信号量来管理有限数量的数据库连接,从而确保有效利用资源而不会造成数据库服务器过载。
import java.util.concurrent.Semaphore;
public class DatabaseConnectionPool {
private final Semaphore semaphore;
public DatabaseConnectionPool ( int maxConnections) {
semaphore = new Semaphore (maxConnections);
}
public void connect () {
try {
semaphore.acquire();
//模拟数据库连接使用
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
}
}
线程池
执行器框架
Java 中的 Executors 框架提供了用于管理线程池的高级 API,使得更轻松高效地处理大量并发任务。
- FixedThreadPool:创建一个具有固定数量线程的线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
private static final int N_THREADS = 5;
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(N_THREADS);
for (int i = 0; i < 10; i++) {
executor.execute(() -> System.out.println("Task executed by " + Thread.currentThread().getName()));
}
executor.shutdown();
}
}
在此示例中,使用缓存线程池来执行任务,并根据需求动态调整线程数量。
- ScheduledThreadPool:创建一个线程池,可以安排命令在给定的延迟后运行或定期执行。
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 < 10; i++) {
executor.execute(() -> System.out.println("Task executed by " + Thread.currentThread().getName()));
}
executor.shutdown();
}
}
在此示例中,使用计划线程池定期执行任务,从初始延迟后一秒开始,然后每三秒执行一次。
真实示例:Web 服务器处理多个客户端请求
Web 服务器可以使用线程池同时处理多个客户端请求,确保高效的资源利用率和更快的响应时间。
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WebServer {
private static final int PORT = 8080;
private static final int THREAD_POOL_SIZE = 10;
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
while (true) {
Socket clientSocket = serverSocket.accept();
executor.execute(new ClientHandler(clientSocket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ClientHandler implements Runnable {
private final Socket clientSocket;
public ClientHandler(Socket clientSocket) {
this.clientSocket = clientSocket;
}
@Override
public void run() {
// Handle client request
}
}
在此示例中,使用固定线程池来管理 Web 服务器,同时处理多个客户端连接。
3. 高级并发实用程序
java.util.concurrent 包
java.util.concurrent 包提供了一组丰富的实用程序,用于高级并发控制。一些最常用的类包括:
- CountDownLatch:允许一个或多个线程等待,直到其他线程正在执行的一组操作完成。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
private static final int N_TASKS = 3;
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(N_TASKS);
for (int i = 0; i < N_TASKS; i++) {
new Thread(new Task(latch)).start();
}
try {
latch.await(); // Main thread waits until all tasks are done
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("All tasks completed.");
}
}
class Task implements Runnable {
private final CountDownLatch latch;
public Task(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
System.out.println("Task executed by " + Thread.currentThread().getName());
latch.countDown(); // Decrement the count of the latch
}
}