Java Concurrent 包使用攻略
Java Concurrent 包提供了一系列并发编程的工具类和接口,用于简化多线程编程、提高并发性能和优化资源利用。在编写高性能的、并发的、安全的多线程应用程序时,Java Concurrent 包是一项必不可少的技术。本文将详细介绍 Java Concurrent 包的常用用法。
基础概念
线程安全性
多线程并发编程中最重要的概念是线程安全性。线程安全性指的是多线程环境下,共享资源能正确、高效地被多个线程同时访问,而不出现数据竞争、内存泄漏等问题。在 Java 中,线程安全性是由以下几个方面保证的:
- 原子性:对于变量的单个读取和写入操作是原子性的,不可被其他线程干扰。
- 可见性:对于变量修改的结果,对其他线程是可见的。
- 有序性:操作的指令执行顺序是有序的,不会因为指令排序等问题出现错误。
内存模型
Java 中的内存模型是描述多线程并发编程中的内存交互的抽象规范。在 Java 内存模型中,每个线程都有自己独立的工作内存,而所有线程共享一个主内存。线程从主内存读取变量到本地内存中进行操作,写回到主内存时,再将本地内存中的操作结果写回。在多核处理器中,不同线程可能在不同的 CPU 上执行。因此,在多线程编程中,需要关注线程之间的同步和内存可见性问题,以避免出现数据竞争和线程间通信问题等。
Concurrent 包常用类和接口
Java Concurrent 包中包含了大量解决并发问题的类和接口,其中比较常用的有以下几个:
Lock 接口
Lock 接口提供了与关键字synchronized类似的互斥实现,但它能够更加灵活地控制线程之间的协作和同步,常用实现类有 ReentrantLock 和 ReentrantReadWriteLock。使用 Lock 接口需要手动显示释放锁,需要搭配 try-finally 或 try-with-resources 以确保锁被正确释放。
Lock lock = new ReentrantLock();
lock.lock();
try {
// 进行加锁操作的线程代码
} finally {
lock.unlock();
}
synchronized 关键字
synchronized 关键字用于同步访问共享资源,在 Java 中是一个基础的同步机制,使用简单。关键字 synchronized 将对象作为锁(monitor)来控制多线程之间的协作和同步。
synchronized (lock) {
//访问共享资源的线程代码
}
Executor 和 ExecutorService 接口
Executor 和 ExecutorService 接口是用于管理线程池的核心接口。Executor 接口负责管理线程的创建、销毁和运行,ExecutorService 接口是 Executor 接口的子类,扩展了线程池的一些功能,比如允许提交 Callable 和 Runnable 任务、设置执行延迟、取消任务等。
ExecutorService executor = Executors.newFixedThreadPool(10);
CountDownLatch 类
CountDownLatch 类是一个倒计时计数器,可以通过计数完成等待线程执行的任务。这个计数器不能被重置,当计数到达零时,所有等待线程就都会被释放。在这之前将会一直阻塞线程,通过 CountDownLatch 可以协调多个线程的并发操作。
CountDownLatch latch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
// 线程任务的执行代码
latch.countDown();
});
}
latch.await();
// 所有子线程任务都执行完毕的后续代码
CyclicBarrier 类
CyclicBarrier 类可以协调多个线程的并发操作,当多个线程都执行到栅栏点,才会继续执行。CyclicBarrier 可以重复使用,重用后设置的栅栏点会重置。
CyclicBarrier barrier = new CyclicBarrier(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
// 线程任务的执行代码
barrier.await();
});
}
// 所有子线程任务都执行完毕的后续代码
示例说明
示例1:计算线程
需求:编写一个利用多线程计算数组元素和的程序,要求多线程共同计算,最后返回结果。
public class Calculator implements Callable<Integer> {
private final int[] numbers;
public Calculator(int[] numbers) {
this.numbers = numbers;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int number : numbers) {
sum += number;
}
return sum;
}
}
使用 Callable 接口实现计算任务,并返回任务的结果。
ExecutorService executor = Executors.newFixedThreadPool(10);
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<Callable<Integer>> tasks = new ArrayList<>();
for (int i = 0; i < 10; i++) {
tasks.add(new Calculator(numbers));
}
List<Future<Integer>> results = executor.invokeAll(tasks);
int sum = 0;
for (Future<Integer> future : results) {
sum += future.get();
}
System.out.println("Total sum: " + sum);
将任务放到线程池中执行后,再从 Future 中获取所有任务结果并计算总和。最终输出:Total sum: 55。
示例2:乘客、司机案例
需求:模拟多个乘客拼车,司机只有在车上人满发车,或时间到了才会出发。
public class Car {
private final int seats = 5;
private final CountDownLatch count = new CountDownLatch(seats);
public void addPassenger() throws InterruptedException {
count.countDown();
}
public void start() {
try {
count.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Car is full. Start!");
}
}
public class Passenger implements Runnable {
private final Car car;
public Passenger(Car car) {
this.car = car;
}
@Override
public void run() {
try {
car.addPassenger();
System.out.println("Passenger added. Current number: " + (car.seats - (int)car.count.getCount()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Driver implements Runnable {
private final Car car;
public Driver(Car car) {
this.car = car;
}
@Override
public void run() {
car.start();
}
}
public class Simulator {
public static void main(String[] args) {
Car car = new Car();
ExecutorService executor = Executors.newFixedThreadPool(6);
for (int i = 0; i < car.seats - 1; i++) {
executor.submit(new Passenger(car));
}
executor.submit(new Driver(car));
executor.shutdown();
}
}
创建一个 Car 类来表示车,有座位数限制和等待候车的旅客计数器。乘客和司机都是一个 Runnable 类,一个 Runnable 对象负责一个线程。通过 CountDownLatch 来实现线程之间的协调。每次加入一个旅客时,让计数器减一,当计数器减为零时,司机就会发车。
总结
Java Concurrent 包提供了丰富的多线程 API,包括锁、线程池、同步工具、线程安全容器等,可以极大地简化多线程编程、提高性能、优化资源利用。但是并发编程本身存在复杂性、难度以及易出现不可预见的问题,需要在开发中加以注意和处理。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:谈谈java的concurrent用法 - Python技术站