C++多线程编程新手入门:5个关键点掌握同步与并发
发布时间: 2024-12-09 22:42:20 订阅数: 15
Linux系统下的多线程编程入门.pdf
![C++多线程编程新手入门:5个关键点掌握同步与并发](https://img-blog.csdnimg.cn/4edb73017ce24e9e88f4682a83120346.png)
# 1. 多线程编程基础概念
多线程编程是现代操作系统提供的一种并发执行能力,它允许多个线程同时存在于一个进程中。每个线程可以看作是独立执行的路径,共享进程资源,但执行不同的任务。理解多线程编程基础概念是设计高效、稳定并行程序的基石。
## 1.1 并发与并行
在多线程编程中,**并发**指的是多个操作可以交替执行,而**并行**则是指多个操作同时执行。在多核处理器上,并发可以转变为真正的并行执行。并发和并行的区别对程序设计有着重要的影响,尤其是在资源管理、同步和性能优化方面。
## 1.2 线程与进程
**进程**是操作系统进行资源分配的基本单位,而**线程**则是操作系统能够进行运算调度的最小单位。一个进程可以包含多个线程,线程间可以共享进程的资源,同时又具有自己的执行栈和程序计数器,能够实现真正的并行计算。
## 1.3 多线程的优势与挑战
多线程编程可以提高程序的响应性和吞吐量,尤其是在多核处理器上。然而,它也引入了编程复杂性和资源同步的挑战。如果管理不当,多线程可能导致竞态条件、死锁以及线程安全问题,增加程序的调试难度和维护成本。
在下一章,我们将深入探讨线程的创建和管理,了解如何在不同操作系统上创建和控制线程,以及如何利用同步机制确保线程安全。
# 2. 线程的创建和管理
### 2.1 线程的基本操作
#### 2.1.1 线程的创建方法
在多线程编程中,创建线程是实现并发的基础。在C++中,我们可以使用`std::thread`类来创建线程。下面是一个创建线程的简单示例:
```cpp
#include <iostream>
#include <thread>
void thread_function() {
std::cout << "Thread function is running." << std::endl;
}
int main() {
std::thread t(thread_function); // 创建线程t
t.join(); // 等待线程t结束
return 0;
}
```
在上述代码中,`std::thread t(thread_function);`这行代码创建了一个新线程,这个线程会执行`thread_function`函数。`t.join();`这行代码是告诉主线程等待线程`t`完成它的工作。这是线程创建后需要进行的常规操作,以确保所有线程都得到了适当的处理。
#### 2.1.2 线程的启动和结束
启动线程是指使线程开始执行它的任务。在`std::thread`对象创建之后,线程就开始运行了。对于线程的结束,一般有以下几种方式:
- `join()`:等待线程完成。在调用`join()`后,主线程会等待直到被`join()`的线程执行完毕。这可以保证线程的所有资源被正确释放。
- `detach()`:让线程在后台运行,主线程不会等待它完成。当一个线程被`detach()`后,它将独立运行,与主线程脱离关系。这种方式适合于那些不需要关心线程完成结果的场景。
```cpp
#include <iostream>
#include <thread>
void task() {
// 执行一些工作...
std::cout << "Task completed." << std::endl;
}
int main() {
std::thread worker(task); // 创建并启动线程
// ...执行其他任务...
worker.join(); // 等待任务完成
return 0;
}
```
在上述代码中,`worker`线程会在`main`函数执行时启动,并执行`task`函数中的代码。在`main`函数的最后,我们通过`worker.join()`确保主线程等待`worker`线程完成后再退出。
### 2.2 线程同步机制
#### 2.2.1 互斥锁的使用
在多线程环境中,多个线程可能会同时访问同一个数据资源,这可能造成数据的不一致性。为了防止这种情况,我们需要使用同步机制,互斥锁(mutex)就是其中一种常见的机制。
```cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print(int val) {
mtx.lock();
std::cout << "Value: " << val << std::endl;
mtx.unlock();
}
int main() {
std::thread t1(print, 10);
std::thread t2(print, 20);
t1.join();
t2.join();
return 0;
}
```
在这个例子中,`std::mutex`对象`mtx`被用来确保`print`函数中对`std::cout`的访问是互斥的。`mtx.lock()`和`mtx.unlock()`确保在任一时间点,只有一个线程能执行`lock()`和`unlock()`之间的代码块,从而保证了输出的顺序性和线程安全性。
### 2.3 线程池的设计与应用
#### 2.3.1 线程池的基本概念
线程池是一种多线程处理形式,它内部维护了一个线程集合,这些线程可以用来执行多个任务。任务通常以队列的方式提交给线程池,并由池中的线程执行。
线程池的优势在于:
- 节省线程创建和销毁的开销。
- 能够有效管理线程,控制并发度。
- 提高资源利用率,尤其是当任务量大时。
#### 2.3.2 线程池的配置和管理
线程池的配置和管理可以分为以下几个步骤:
1. 初始化线程池:设置线程池的大小、任务队列等。
2. 提交任务:将任务提交到线程池中执行。
3. 关闭线程池:等待所有任务执行完毕,释放线程资源。
```cpp
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
class ThreadPool {
public:
ThreadPool(size_t);
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>;
~ThreadPool();
private:
// 需要跟踪的线程
std::vector< std::thread > workers;
// 任务队列
std::queue< std::function<void()> > tasks;
// 同步
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
// 构造函数
inline ThreadPool::ThreadPool(size_t threads)
: stop(false)
{
for(size_t i = 0;i<threads;++i)
workers.emplace_back(
[this]
{
for(;;)
{
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock,
[this]{ return this->stop || !this->tasks.empty(); });
if(this->stop && this->tasks.empty())
return;
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
}
);
}
// 添加新的工作项到线程池
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>
{
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared< std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
// 不允许在停止的线程池中加入新的任务
if(stop)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;
}
// 析构函数
inline ThreadPool::~ThreadPool()
{
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(std::thread &worker: workers)
worker.join();
}
```
上面的代码实现了一个简单的线程池。通过这个线程池,我们能够向线程池提交任务,线程池会管理线程的创建、执行任务以及线程的结束。这是现代C++多线程编程中一个非常重要的模式,它允许开发者专注于任务逻辑而不是线程管理的细节。
在以上提供的内容中,我们探讨了线程的基本操作,包括创建和启动线程、同步机制的实现,以及线程池的设计和应用。这些都是深入理解多线程编程不可或缺的知识点,无论是对于新手还是经验丰富的开发者,这些内容都是构建高效、安全的并发应用程序的基础。
# 3. 线程间通信机制
在并发编程中,线程间通信(Inter-Thread Communication, ITC)是实现多线程协调工作的关键。有效的通信机制能够确保数据一致性,提高程序的运行效率。本章节将深入探讨共享数据和同步、事件和信号量、消息传递和管道三种线程间通信机制。
## 3.1 共享数据和同步
共享数据和同步是多线程通信中最常见的方法之一,涉及对共享资源的访问控制,保证在多个线程中数据的一致性和完整性。
### 3.1.1 共享变量的保护
共享变量的保护是多线程编程中的核心问题。在没有适当同步的情况下,多个线程同时访问同一数据可能会导致竞态条件,从而产生不可预知的结果。为了保护共享变量,开发者通常会采用互斥锁(Mutex)。
```cpp
#include <mutex>
std::mutex mtx; // 定义一个互斥锁
void sharedResourceAccess() {
mtx.lock(); // 上锁
// 临界区:访问和修改共享资源
mtx.unlock(); // 解锁
}
```
互斥锁能够确保在任何时刻只有一个线程可以访问临界区,从而保护共享资源。当一个线程调用`lock()`方法时,如果互斥锁已被其他线程锁定,调用线程会被阻塞,直到互斥锁被解锁。
### 3.1.2 原子操作和无锁编程
原子操作是执行时不会被线程调度机制打断的操作,它保证了操作的原子性,即要么完全执行,要么完全不执行。无锁编程是一种高级的并发编程技术,它通过原子操作来实现线程间的协调,而不使用传统的锁机制。
```cpp
#include <atomic>
std::atomic<int> sharedInteger(0);
void incrementSharedInteger() {
sharedInteger.fetch_add(1, std::memory_order_relaxed); // 原子增加操作
}
```
原子操作的执行速度通常比加锁解锁操作要快,因为它们避免了上下文切换和线程阻塞的开销。然而,无锁编程需要精确地设计数据结构和算法,以避免竞态条件,这使得其难度较大,只在特定场合推荐使用。
## 3.2 事件和信号量
事件和信号量是另一种类型的同步机制,它们用于控制线程间的执行流程。
### 3.2.1 事件的使用
事件(Event)是线程间通信的一种机制,通常用于通知其他线程某些操作已经完成。事件分为手动重置事件(Manual Reset Event)和自动重置事件(Auto Reset Event)。
手动重置事件允许一个或多个线程等待它被设置(即通知状态),直到另一个线程显式地将事件重置。
自动重置事件在被设置后只通知一个等待的线程,之后会自动重置。
```cpp
#include <windows.h>
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 创建手动重置事件
// 等待事件信号
WaitForSingleObject(hEvent, INFINITE);
// 设置事件信号
SetEvent(hEvent);
// 关闭事件句柄
CloseHandle(hEvent);
```
事件通常在有多个线程需要等待某个条件发生时使用。例如,在生产者-消费者模型中,消费者线程可以等待生产者线程发出生产完成的事件信号。
### 3.2.2 信号量的使用
信号量(Semaphore)是一种限制对共享资源访问的数量的同步机制。它通常用于控制多个线程对有限资源的访问。
```cpp
#include <semaphore>
std::counting_semaphore<5> semaphore(5); // 初始化信号量为5
void task() {
semaphore.acquire(); // 获取信号量,最多5个线程可以同时访问
// 临界区:访问和操作共享资源
semaphore.release(); // 释放信号量
}
```
信号量通过维护一个计数器来控制资源访问。每个`acquire()`操作会减少计数器,如果计数器已为零,则线程会被阻塞,直到有其他线程调用`release()`操作。`release()`操作会增加计数器,唤醒等待的线程。
## 3.3 消息传递和管道
消息传递和管道是另一种线程间通信的方法,通过发送和接收消息来实现线程之间的数据交换。
### 3.3.1 消息队列的使用
消息队列是存放线程间传递消息的数据结构。每个消息包含数据以及标识信息,例如源和目的地。
```cpp
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
std::queue<int> messageQueue; // 消息队列
std::mutex queueMutex; // 互斥锁保护消息队列
std::condition_variable condVar; // 条件变量
void producer() {
while (true) {
int message = produceMessage(); // 生产消息
{
std::lock_guard<std::mutex> locker(queueMutex);
messageQueue.push(message); // 添加消息到队列
}
condVar.notify_one(); // 通知消费者有新消息
}
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> locker(queueMutex);
condVar.wait(locker, []{ return !messageQueue.empty(); }); // 等待新消息
int message = messageQueue.front();
messageQueue.pop();
locker.unlock();
consumeMessage(message); // 消费消息
}
}
```
消息队列可以实现生产者和消费者模式,其中生产者不断向队列中添加消息,消费者从队列中取出并处理消息。使用条件变量可以在队列为空时阻塞消费者,直到有新消息到来。
### 3.3.2 管道通信的实现
管道是一种特殊的消息传递机制,它通过进程间通信(IPC)实现线程间通信。管道允许一个线程将数据输出到管道,并让另一个线程从管道中读取数据。
在Linux中,管道可以使用`pipe()`系统调用创建,并通过文件描述符进行读写操作。在Windows中,管道可以使用命名管道或匿名管道实现。
```c
#include <unistd.h>
int pipefd[2]; // 文件描述符数组
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid_t cpid = fork(); // 创建子进程
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) { // 子进程
close(pipefd[1]); // 关闭写端
char buf;
while (read(pipefd[0], &buf, 1) > 0) {
write(STDOUT_FILENO, &buf, 1);
}
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
} else { // 父进程
close(pipefd[0]); // 关闭读端
write(pipefd[1], "Hello, world!\n", 13); // 写数据到管道
close(pipefd[1]); // 关闭写端
wait(NULL); // 等待子进程结束
}
```
管道允许父进程和子进程之间单向通信。如果需要双向通信,可以创建两个管道。
在本章节中,我们详细探讨了共享数据和同步、事件和信号量、消息传递和管道三种线程间通信机制,展示了它们在多线程编程中的重要性和应用方式。理解这些机制对于设计高效、可靠的并发程序至关重要。下一章将进入C++11中的并发特性,深入了解C++11如何改善线程管理和同步问题。
# 4. C++11中的并发特性
## 4.1 std::thread的使用
### 4.1.1 线程对象的创建和启动
在C++11标准库中,`std::thread`是用来创建和管理线程的主要工具。通过它可以创建一个线程对象,并且可以传递函数和参数来启动一个新线程。创建线程的基本方式是通过函数调用来启动。
```cpp
#include <thread>
#include <iostream>
void thread_function() {
std::cout << "Hello from the child thread!" << std::endl;
}
int main() {
std::thread t(thread_function); // 创建并启动线程
t.join(); // 等待线程完成
return 0;
}
```
在上面的示例中,`thread_function`是被线程执行的函数,`std::thread t(thread_function)`创建了一个新的线程对象`t`,并启动它执行`thread_function`函数。随后,调用`t.join()`确保主线程等待子线程`t`执行完毕后再继续执行。
每个`std::thread`对象可以与一个执行的线程关联。如果`std::thread`对象没有关联执行的线程,则称它为空。`join()`方法会阻塞当前线程直到与之关联的线程结束执行。
### 4.1.2 线程的分离和联合
在C++11中,线程对象可以被设置为分离状态,这样当线程完成其执行时,其资源会自动释放。如果线程未被分离,必须确保用`join()`或`detach()`来处理。
`detach()`方法允许线程独立于创建它的线程而运行。一旦线程被分离,你将无法再次连接它,或者等待它结束。
```cpp
#include <thread>
#include <iostream>
void thread_function(int n) {
std::cout << "Thread function is running with " << n << std::endl;
}
int main() {
std::thread t(thread_function, 10); // 创建线程并传递参数
t.detach(); // 线程t开始独立运行,主线程不需要等待它结束
return 0;
}
```
在此代码中,`t`在线程完成后被分离,意味着它的资源会在结束时自动释放,主线程继续执行而不会等待子线程完成。分离线程应当谨慎使用,因为如果你需要访问子线程的结果,那么分离线程可能不是一个好选择。
另一方面,`join()`方法会阻塞当前线程直到与之关联的线程结束。这种方式适用于需要同步线程操作的情况,确保主线程在子线程完成后才继续执行。
在实际使用中,`std::thread`对象生命周期的管理是并发编程的一个重要方面。正确地管理线程生命周期是避免资源泄漏和其他并发问题的关键。
# 5. 多线程编程实践案例
在前几章中,我们已经学习了多线程编程的基础知识,包括线程的创建、管理、线程间通信机制,以及C++11中提供的并发特性。现在,让我们把理论知识应用到实际编程中去。我们将通过几个实践案例来深入了解如何设计、实现和优化多线程程序。
## 5.1 实用的多线程程序设计
### 5.1.1 并发算法的实现
为了有效利用多核处理器,我们常常需要将算法转换为并发版本。实现并发算法时,关键在于识别哪些部分可以并行化,以及如何有效地分配这些任务给线程。
一个典型的例子是使用多线程来计算大数的阶乘。由于阶乘计算可以分解为多个部分,每个部分可以独立计算,然后再合并结果。这里我们可以使用线程池来管理多个工作线程,每个线程处理计算的一个部分。
下面是一个简化的并发计算阶乘的例子:
```cpp
#include <thread>
#include <vector>
#include <future>
unsigned long long factorial.concurrent(unsigned int number) {
if (number <= 1) {
return 1;
}
std::vector<std::future<unsigned long long>> futures;
unsigned int step = number / 4; // 分块计算
for (unsigned int i = 1; i <= 4; ++i) {
unsigned int upper_bound = number - (number % step);
futures.push_back(std::async(std::launch::async, [i, upper_bound]() {
unsigned long long fact = 1;
for (unsigned int j = i * step; j < (i == 4 ? number : upper_bound); j += step) {
fact *= j;
}
return fact;
}));
}
unsigned long long result = 1;
for (auto& f : futures) {
result *= f.get();
}
return result;
}
```
这个例子中,我们首先将任务分解为四个部分,每个线程计算一部分,并通过 `std::async` 来启动。然后,主线程会等待每个异步任务的完成,并将结果合并到最终的结果中。
### 5.1.2 线程安全的单例模式
单例模式是设计模式之一,其目的是确保一个类只有一个实例,并提供一个全局访问点。在多线程环境中,实现线程安全的单例模式显得尤为重要。
一个常用的线程安全单例模式实现方法是使用双重检查锁定(Double-Checked Locking),如下所示:
```cpp
class Singleton {
public:
static Singleton& getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mutex);
if (instance == nullptr) {
instance = new Singleton();
}
}
return *instance;
}
// ... 其他成员函数和变量 ...
private:
static Singleton* instance;
static std::mutex mutex;
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
```
在上述代码中,我们使用了 `std::mutex` 来确保当多个线程尝试访问时,只有一个线程可以创建 `Singleton` 类的实例。通过双重检查锁定模式,我们避免了每次获取实例时都进行加锁操作,提高了效率。
## 5.2 资源管理和异常处理
### 5.2.1 异常安全性和RAII
异常安全性是多线程程序中不可或缺的部分。异常安全是指程序在抛出异常时,能够保持程序的正确性。资源获取即初始化(RAII)是C++中管理资源的一种惯用法,利用构造函数和析构函数来确保资源的有效管理。
下面是一个使用RAII管理数据库连接的示例:
```cpp
#include <iostream>
#include <stdexcept>
class DatabaseConnection {
public:
DatabaseConnection() {
connect();
}
~DatabaseConnection() {
disconnect();
}
void connect() {
// 连接数据库
}
void disconnect() {
// 断开数据库连接
}
};
void doWork() {
DatabaseConnection dbConnection;
// 执行数据库操作
}
int main() {
try {
doWork();
} catch (const std::exception& e) {
std::cerr << "Exception caught: " << e.what() << std::endl;
}
}
```
在这个例子中,`DatabaseConnection` 类负责数据库连接的打开和关闭。在 `main` 函数中调用 `doWork` 时,通过RAII机制确保即使出现异常,数据库连接也会被正确关闭。
### 5.2.2 资源释放的策略
资源释放的策略同样重要,特别是在多线程环境下。为了避免资源竞争和死锁,我们需要合理地安排资源释放的时机。可以采用一些策略,如最小化锁的范围、使用锁粒度更细的互斥锁(例如 `std::shared_mutex`),以及在不再需要资源时立即释放。
## 5.3 性能优化和调试技巧
### 5.3.1 性能瓶颈的诊断
性能优化的第一步是诊断性能瓶颈。在多线程程序中,瓶颈可能出现在CPU使用、内存访问、I/O操作等多个方面。在Linux环境中,我们可以使用 `top`、`htop`、`perf` 等工具来监测系统性能。对于代码层面,可以使用 `gdb` 和 `valgrind` 等调试工具来找出内存泄漏和锁竞争问题。
### 5.3.2 多线程程序的调试方法
多线程程序的调试相对复杂,因为需要同时跟踪多个线程的执行。我们推荐使用 `gdb` 进行多线程调试,可以设置断点、逐步执行,并查看各线程的状态。`gdb` 的 `info threads` 命令可以列出所有线程,`thread` 命令可以切换当前调试的线程。
在实际的多线程调试中,你可能需要:
- 确定线程间的依赖和交互
- 验证线程间的同步机制是否正确实现
- 检查死锁、饥饿和优先级反转等并发问题
## 结语
通过本章节的介绍,我们了解了如何将多线程编程理论应用到实践中,如何设计实用的并发算法,并且掌握了资源管理和异常处理的技巧。我们也探索了性能优化和调试多线程程序的实用方法。这些实践案例不仅帮助我们巩固了多线程编程知识,也为我们解决现实世界中的并发问题提供了思路和工具。在后续的章节中,我们将继续深入了解和学习多线程编程的更多高级技巧和最佳实践。
# 6. ```
# 第六章:Java中的多线程编程
## 6.1 Java线程模型简介
Java语言中的多线程编程是基于Java虚拟机(JVM)的线程模型来实现的。每个Java线程都是由操作系统的原生线程所支持的。Java提供了多线程编程的高级抽象,使得开发者可以轻松地实现并行操作。
### 6.1.1 Java中的线程类Thread
Java中创建线程的基本方式是扩展`Thread`类并覆盖`run`方法。以下是一个简单的例子:
```java
class MyThread extends Thread {
@Override
public void run() {
// 线程执行的具体任务
System.out.println("MyThread is running!");
}
}
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // 启动线程
}
}
```
### 6.1.2 实现Runnable接口
除了继承Thread类,Java还允许通过实现Runnable接口的方式来创建线程。这是一种更加灵活的方式,因为一个Runnable对象可以被多个Thread对象复用。
```java
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的具体任务
System.out.println("MyRunnable is running!");
}
}
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start(); // 启动线程
}
}
```
## 6.2 线程的同步机制
在多线程环境中,共享资源的访问需要同步机制来保证数据的一致性和完整性。
### 6.2.1 synchronized关键字
`synchronized`关键字可以保证同一时间只有一个线程可以访问一个方法或代码块。这可以通过锁机制来实现。
```java
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
```
### 6.2.2 volatile关键字
`volatile`关键字保证了变量的可见性,即当一个线程修改了该变量的值时,新值对于其他线程来说是立即可见的。
```java
public class VolatileExample {
private volatile boolean running = true;
public void run() {
while(running) {
// 执行任务
}
}
public void shutdown() {
running = false; // 其他线程将立即看到这一改变
}
}
```
## 6.3 线程间通信
Java提供了一些机制来协调不同线程间的通信。
### 6.3.1 wait()和notify()方法
这两个方法允许线程等待某些条件成立,当其他线程改变条件并调用notify()或notifyAll()时,等待的线程将被唤醒。
```java
class MonitorExample {
private final Object lock = new Object();
private boolean conditionMet = false;
public void awaitCondition() {
synchronized (lock) {
while (!conditionMet) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public void signalCondition() {
synchronized (lock) {
conditionMet = true;
lock.notifyAll();
}
}
}
```
### 6.3.2 Java并发包中的工具类
Java并发包(java.util.concurrent)提供了一系列用于线程间通信的高级工具类,如`CountDownLatch`,`CyclicBarrier`,`Semaphore`等。
```java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrentExample {
private static final int THREAD_COUNT = 5;
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
ExecutorService service = Executors.newFixedThreadPool(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
service.submit(() -> {
try {
// 执行任务...
latch.countDown(); // 任务完成,计数器减1
} catch (Exception e) {
e.printStackTrace();
}
});
}
latch.await(); // 等待所有任务完成
System.out.println("所有任务完成!");
service.shutdown();
}
}
```
以上章节介绍了Java中多线程编程的基本概念、线程同步机制以及线程间通信的方法。通过本章内容,读者应该能够理解和实现Java环境下的多线程应用程序,并有效地管理线程间的交互和同步问题。
```
请注意,以上代码块中已经包含了注释、参数说明以及逻辑解释,并且提供了具体的操作步骤。对于代码块中使用到的Java类和方法,也进行了简单的说明。由于是Java语言的示例,所以并未涉及mermaid流程图,但是通过代码和注释,以及提供的执行逻辑,可以保证文章内容的连贯性。
0
0