并发编程在现代软件开发中越来越重要,随着硬件多核处理器的普及,充分利用多核处理器的能力可以显著提升程序的运行效率和响应速度。Java并发编程提供了丰富的API,使得编写高效的并发程序变得更加简单。掌握Java并发编程不仅可以提高程序效率,还能增强程序的可扩展性和稳定性。
Java并发编程的核心概念
- 线程(Thread):线程是程序执行的一个执行单元,Java中的每个线程都是一个Thread对象。
- 同步(Synchronization):确保多个线程在访问共享资源时不会出现数据不一致的问题。
- 并发容器(Concurrent Containers):专门用于多线程环境下的容器,提供线程安全的访问操作。
- 并发工具类:包括
synchronized
关键字、volatile
关键字、Lock
接口及Condition
接口等。
Java并发编程的工具类介绍
Java并发工具类是Java并发编程的基础,掌握这些工具类是编写高效并发程序的关键。
- synchronized关键字:用于同步对象,确保同一时刻只有一个线程可以访问该对象。
- volatile关键字:确保变量的修改对所有线程可见,主要用于保证变量的可见性和多线程之间的通信。
- Lock接口和Condition接口:提供了比
synchronized
更灵活的同步机制,支持条件变量和更复杂的锁策略。
示例代码
public class SyncExample {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
System.out.println("Incremented count to: " + count);
}
}
public void decrement() {
synchronized (lock) {
count--;
System.out.println("Decremented count to: " + count);
}
}
}
线程基础
创建和启动线程
Java中创建和启动线程的基本步骤如下:
- 创建一个实现
Runnable
接口的类或直接使用Thread
类。 - 在
Runnable
实现类或Thread
类中重写run
方法。 - 创建线程对象并调用
start
方法启动线程。
示例代码:
public class ThreadExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread 1: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread-1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread 2: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread-2");
t1.start();
t2.start();
}
}
线程的生命周期
Java线程有多种状态,包括:
- 新建状态:线程对象被创建,但还没有调用
start
方法。 - 可运行状态:线程被调度,等待CPU运行。
- 运行状态:线程正在执行
run
方法中的代码。 - 阻塞状态:线程被阻塞,等待某个事件发生,如等待I/O操作完成。
- 等待状态:调用
wait
方法,进入等待状态。 - 终止状态:线程已完成执行,或异常终止。
线程同步与互斥
线程同步和互斥是确保多线程环境下资源安全访问的关键机制。
线程同步
线程同步用于保证多个线程在访问共享资源时不会出现数据不一致的问题。Java提供了多种方式实现线程同步:
- synchronized关键字:用于方法或代码块实现同步。
- ReentrantLock类:提供了比
synchronized
更灵活的同步机制。
示例代码:
public class SyncExample {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
System.out.println("Incremented count to: " + count);
}
}
public void decrement() {
synchronized (lock) {
count--;
System.out.println("Decremented count to: " + count);
}
}
}
常见并发问题及其解决方法
死锁
死锁是指两个或多个线程在等待对方释放资源,导致所有线程都无法继续执行的情况。避免死锁的方法包括:
- 避免资源循环等待:确保每个线程获取资源的顺序一致。
- 超时等待:使用
tryLock
方法尝试获取锁,避免长时间等待。 - 死锁检测与恢复:定期检测系统中的死锁,如果检测到死锁,强制释放资源。
示例代码:
public class DeadlockExample {
private final Object resourceA = new Object();
private final Object resourceB = new Object();
public void methodA() {
synchronized (resourceA) {
synchronized (resourceB) {
System.out.println("Method A");
}
}
}
public void methodB() {
synchronized (resourceB) {
synchronized (resourceA) {
System.out.println("Method B");
}
}
}
}
资源竞争
资源竞争是指多个线程同时访问共享资源导致数据不一致的问题。解决资源竞争的方法包括:
- 同步代码块:使用
synchronized
关键字或ReentrantLock
类同步访问共享资源。 - 原子操作:使用原子变量
AtomicInteger
等类实现线程安全的原子操作。 - 并发容器:使用并发容器如
ConcurrentHashMap
等提供线程安全的访问。
示例代码:
public class ResourceCompetitionExample {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
public void decrement() {
synchronized (this) {
count--;
}
}
public int getCount() {
return count;
}
}
数据一致性
数据一致性是指多个线程访问共享资源时,数据的状态保持一致。保证数据一致性的方法包括:
- 使用
synchronized
关键字:确保同一时刻只有一个线程访问共享资源。 - 使用
volatile
关键字:确保变量的修改对所有线程可见。 - 使用并发容器:使用并发容器如
ConcurrentHashMap
等提供线程安全的访问。
示例代码:
public class DataConsistencyExample {
private volatile int count = 0;
public void increment() {
count++;
}
public void decrement() {
count--;
}
public int getCount() {
return count;
}
}
Java并发工具类详解
synchronized关键字
synchronized
关键字用于同步方法或代码块,确保同一时刻只有一个线程访问共享资源。synchronized
关键字有两种使用方式:
- 同步方法:直接将方法声明为
synchronized
。 - 同步代码块:将需要同步的代码块用
synchronized
关键字包裹。
示例代码:
public class SyncMethodExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public synchronized int getCount() {
return count;
}
}
volatile关键字
volatile
关键字用于保证变量的修改对所有线程可见,主要用于保证变量的可见性和多线程之间的通信。volatile
变量的修改不会被编译器优化,确保每次读取到最新的值。
示例代码:
public class VolatileExample {
private volatile boolean flag = false;
public void setFlag() {
flag = true;
}
public boolean getFlag() {
return flag;
}
}
Lock接口和Condition接口
Lock
接口提供了比synchronized
更灵活的同步机制,支持条件变量和更复杂的锁策略。Condition
接口用于实现条件变量,可以等待特定条件满足后再继续执行。
示例代码:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class ReentrantLockExample {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean flag = false;
public void setFlag() {
lock.lock();
try {
flag = true;
condition.signalAll();
} finally {
lock.unlock();
}
}
public void waitFlag() {
lock.lock();
try {
while (!flag) {
condition.await();
}
System.out.println("Flag is set");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
并发容器与集合类
并发容器的特点
并发容器是专门为多线程环境设计的容器,提供线程安全的访问操作。常见的并发容器包括ConcurrentHashMap
、ConcurrentLinkedQueue
等。
- ConcurrentHashMap:线程安全的哈希表,提供了比
HashMap
更好的并发性能。 - ConcurrentLinkedQueue:线程安全的链表队列,支持高效的并发访问。
使用场景及示例代码
ConcurrentHashMap
ConcurrentHashMap
适用于高并发读写场景,可以保证线程安全。
示例代码:
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
private final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
public void put(String key, String value) {
map.put(key, value);
}
public String get(String key) {
return map.get(key);
}
public void remove(String key) {
map.remove(key);
}
}
ConcurrentLinkedQueue
ConcurrentLinkedQueue
适用于需要高效并发访问的队列场景。
示例代码:
import java.util.concurrent.ConcurrentLinkedQueue;
public class ConcurrentLinkedQueueExample {
private final ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
public void add(int value) {
queue.add(value);
}
public Integer poll() {
return queue.poll();
}
}
常用的并发集合类介绍
ConcurrentHashMap
ConcurrentHashMap
是线程安全的哈希表,提供了高效并发访问。ConcurrentHashMap
内部使用多个锁,每个锁只保护一部分数据,从而提高了并发性能。
ConcurrentLinkedQueue
ConcurrentLinkedQueue
是线程安全的链表队列,支持高效的并发访问。ConcurrentLinkedQueue
内部使用无锁算法,避免了锁的竞争。
ConcurrentSkipListMap
ConcurrentSkipListMap
是线程安全的跳表实现,支持高效的并发访问。ConcurrentSkipListMap
适用于需要有序访问的数据结构。
分析需求
假设我们需要设计一个简单的高并发服务,该服务需要支持高并发的读写操作,例如统计访问次数、记录日志等。服务需要保证线程安全,避免数据不一致的问题。
设计方案
- 使用并发容器:使用
ConcurrentHashMap
存储访问次数和日志信息。 - 线程同步:使用
synchronized
关键字或ReentrantLock
类实现线程同步。 - 异步处理:使用线程池处理并发请求,提高系统响应速度。
代码实现与优化
统计访问次数
import java.util.concurrent.ConcurrentHashMap;
public class HighConcurrencyService {
private final ConcurrentHashMap<String, Integer> accessCountMap = new ConcurrentHashMap<>();
public void incrementAccessCount(String key) {
accessCountMap.put(key, accessCountMap.getOrDefault(key, 0) + 1);
}
public int getAccessCount(String key) {
return accessCountMap.getOrDefault(key, 0);
}
public void decrementAccessCount(String key) {
Integer count = accessCountMap.get(key);
if (count != null && count > 0) {
accessCountMap.put(key, count - 1);
}
}
}
记录日志
import java.util.concurrent.ConcurrentLinkedQueue;
public class LogService {
private final ConcurrentLinkedQueue<String> logQueue = new ConcurrentLinkedQueue<>();
public void log(String message) {
logQueue.add(message);
}
public String pollLog() {
return logQueue.poll();
}
}
使用线程池处理并发请求
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HighConcurrencyServiceExample {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
public void handleRequest(Runnable requestHandler) {
executor.submit(requestHandler);
}
public void shutdown() {
executor.shutdown();
}
}
完整示例代码
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HighConcurrencyServiceExample {
private final HighConcurrencyService highConcurrencyService = new HighConcurrencyService();
private final LogService logService = new LogService();
private final ExecutorService executor = Executors.newFixedThreadPool(10);
public void handleRequest(Runnable requestHandler) {
executor.submit(() -> {
try {
requestHandler.run();
} finally {
logService.log("Request handled");
}
});
}
public void shutdown() {
executor.shutdown();
}
public static void main(String[] args) {
HighConcurrencyServiceExample service = new HighConcurrencyServiceExample();
for (int i = 0; i < 10; i++) {
service.handleRequest(() -> {
highConcurrencyService.incrementAccessCount("example");
System.out.println("Access count: " + highConcurrencyService.getAccessCount("example"));
});
}
service.shutdown();
}
}
通过以上示例代码,我们可以看到如何设计一个简单的高并发服务,包括使用并发容器、线程同步和异步处理等技术,确保服务能够在高并发情况下稳定运行。