深入理解 Concurrent Java
简介
在当今多核处理器广泛应用的时代,并发编程成为了提高应用程序性能和响应性的关键技术。Java 作为一门主流的编程语言,提供了丰富的并发编程支持,即 Concurrent Java。本文将详细介绍 Concurrent Java 的基础概念、使用方法、常见实践以及最佳实践,帮助读者更好地掌握这一重要的编程领域。
目录
- 基础概念
- 并发与并行
- 线程与进程
- 共享资源与竞态条件
- 使用方法
- 创建线程
- 线程同步
- 线程池
- 常见实践
- 生产者 - 消费者模型
- 并发集合
- 最佳实践
- 避免死锁
- 性能优化
- 并发安全设计
- 小结
- 参考资料
基础概念
并发与并行
- 并发:指在同一时间段内,多个任务都在执行,但不一定是同时执行。操作系统通过时间片轮转等调度算法,让多个任务交替执行,给用户一种多个任务同时进行的错觉。
- 并行:指在同一时刻,多个任务真正地同时执行。这需要多核处理器的支持,每个核心可以同时处理一个任务。
线程与进程
- 进程:是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间和系统资源。
- 线程:是进程中的一个执行单元,是 CPU 调度和分派的基本单位。一个进程可以包含多个线程,它们共享进程的内存空间和系统资源。
共享资源与竞态条件
- 共享资源:多个线程可以访问和修改的资源,如内存中的变量、文件等。
- 竞态条件:当多个线程同时访问和修改共享资源时,由于线程执行顺序的不确定性,可能会导致程序出现不可预测的结果,这种情况称为竞态条件。
使用方法
创建线程
在 Java 中,创建线程有两种常见方式:继承 Thread
类和实现 Runnable
接口。
继承 Thread
类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("This is a thread created by extending Thread class.");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
实现 Runnable
接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("This is a thread created by implementing Runnable interface.");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
线程同步
为了避免竞态条件,需要对共享资源的访问进行同步。Java 提供了多种线程同步机制,如 synchronized
关键字、Lock
接口等。
使用 synchronized
关键字
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}
使用 Lock
接口
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}
线程池
线程池是一种管理和复用线程的机制,可以避免频繁创建和销毁线程带来的开销。Java 提供了 ExecutorService
接口和 ThreadPoolExecutor
类来实现线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + " is running.");
});
}
executorService.shutdown();
}
}
常见实践
生产者 - 消费者模型
生产者 - 消费者模型是一种常见的并发设计模式,用于解决生产者和消费者之间的数据共享问题。
import java.util.LinkedList;
import java.util.Queue;
class Producer implements Runnable {
private Queue<Integer> queue;
private int capacity;
public Producer(Queue<Integer> queue, int capacity) {
this.queue = queue;
this.capacity = capacity;
}
@Override
public void run() {
int value = 0;
while (true) {
synchronized (queue) {
while (queue.size() == capacity) {
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.add(value++);
System.out.println("Produced: " + value);
queue.notify();
}
}
}
}
class Consumer implements Runnable {
private Queue<Integer> queue;
public Consumer(Queue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int value = queue.poll();
System.out.println("Consumed: " + value);
queue.notify();
}
}
}
}
public class Main {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
int capacity = 5;
Thread producerThread = new Thread(new Producer(queue, capacity));
Thread consumerThread = new Thread(new Consumer(queue));
producerThread.start();
consumerThread.start();
}
}
并发集合
Java 提供了一系列线程安全的并发集合类,如 ConcurrentHashMap
、CopyOnWriteArrayList
等。
import java.util.concurrent.ConcurrentHashMap;
public class Main {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
Thread thread1 = new Thread(() -> {
map.put("key1", 1);
map.put("key2", 2);
});
Thread thread2 = new Thread(() -> {
System.out.println("Value of key1: " + map.get("key1"));
map.put("key3", 3);
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(map);
}
}
最佳实践
避免死锁
死锁是并发编程中一种严重的问题,当两个或多个线程相互等待对方释放资源时,就会发生死锁。为了避免死锁,可以采取以下措施:
- 尽量减少锁的使用范围和时间。
- 按照相同的顺序获取锁。
- 使用定时锁(如 tryLock
方法)。
性能优化
- 合理使用线程池,根据任务的类型和数量调整线程池的大小。
- 减少线程之间的竞争,尽量将共享资源的访问限制在单个线程内。
- 使用无锁数据结构,如
ConcurrentHashMap
的某些操作采用了无锁算法,性能更高。
并发安全设计
- 使用线程安全的类和方法,避免使用非线程安全的类在多线程环境中。
- 对共享资源的访问进行充分的同步和验证,确保数据的一致性和完整性。
小结
Concurrent Java 为开发高效、可靠的并发应用程序提供了丰富的支持。通过掌握基础概念、使用方法、常见实践和最佳实践,开发者可以更好地利用多核处理器的优势,提高应用程序的性能和响应性。同时,需要注意并发编程中的陷阱,如竞态条件和死锁,以确保程序的正确性和稳定性。
参考资料
- Oracle Java Documentation - Concurrency
- 《Effective Java》by Joshua Bloch
- 《Java Concurrency in Practice》by Brian Goetz et al.