C++多线程编程新手入门:5个关键点掌握同步与并发

发布时间: 2024-12-09 22:42:20 订阅数: 15
RAR

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流程图,但是通过代码和注释,以及提供的执行逻辑,可以保证文章内容的连贯性。
corwn 最低0.47元/天 解锁专栏
买1年送1年
点击查看下一篇
profit 百万级 高质量VIP文章无限畅学
profit 千万级 优质资源任意下载
profit C知道 免费提问 ( 生成式Al产品 )

相关推荐

SW_孙维

开发技术专家
知名科技公司工程师,开发技术领域拥有丰富的工作经验和专业知识。曾负责设计和开发多个复杂的软件系统,涉及到大规模数据处理、分布式系统和高性能计算等方面。
专栏简介
专栏《C++与系统编程的结合》深入探讨了C++在系统编程领域的应用,提供了20个实用秘籍。文章涵盖了C++异常处理、系统调用优化、C++新特性应用、Linux下C++编程实践、嵌入式系统编程、内核模块与驱动开发、智能指针、调试技巧、操作系统API调用、内存池技术、操作系统开发中的应用以及模板元编程等高级主题。这些文章旨在帮助开发者掌握系统编程的精髓,提升软件开发效率和系统性能。
最低0.47元/天 解锁专栏
买1年送1年
百万级 高质量VIP文章无限畅学
千万级 优质资源任意下载
C知道 免费提问 ( 生成式Al产品 )

最新推荐

【cx_Oracle专家教程】:解锁高级查询、存储过程及并发控制秘籍

![【cx_Oracle专家教程】:解锁高级查询、存储过程及并发控制秘籍](https://opengraph.githubassets.com/690e09e1e3eb9c2ecd736e5fe0c0466f6aebd2835f29291385eb81e4d5ec5b32/oracle/python-cx_Oracle) 参考资源链接:[cx_Oracle使用手册](https://wenku.csdn.net/doc/6476de87543f84448808af0d?spm=1055.2635.3001.10343) # 1. cx_Oracle库概述与安装配置 cx_Oracle是P

ZMODEM协议深入解析:掌握历史、工作原理及应用的关键点

![ZMODEM协议深入解析:掌握历史、工作原理及应用的关键点](https://opengraph.githubassets.com/56daf88301d37a7487bd66fb460ab62a562fa66f5cdaeb9d4e183348aea6d530/cxmmeg/Ymodem) 参考资源链接:[ZMODEM传输协议深度解析](https://wenku.csdn.net/doc/647162cdd12cbe7ec3ff9be7?spm=1055.2635.3001.10343) # 1. ZMODEM协议的历史背景和发展 ## 1.1 ZMODEM的起源 ZMODEM协议作

【7步搞定】创维E900 4K机顶盒新手快速入门指南:界面全解析

![【7步搞定】创维E900 4K机顶盒新手快速入门指南:界面全解析](https://i2.hdslb.com/bfs/archive/8e675ef30092f7a00741be0c2e0ece31b1464624.png@960w_540h_1c.webp) 参考资源链接:[创维E900 4K机顶盒快速配置指南](https://wenku.csdn.net/doc/645ee5ad543f844488898b04?spm=1055.2635.3001.10343) # 1. 创维E900 4K机顶盒开箱体验 ## 简介 作为新兴家庭娱乐设备的代表之一,创维E900 4K机顶盒以其强

揭秘航空数据网络:AFDX协议与ARINC664第7部分实战指南

![揭秘航空数据网络:AFDX协议与ARINC664第7部分实战指南](https://www.techsat.com/web/image/23294-7f34f9c8/TechSAT_PortGateAFDX-diagram.png) 参考资源链接:[AFDX协议/ARINC664中文详解:飞机数据网络](https://wenku.csdn.net/doc/66azonqm6a?spm=1055.2635.3001.10343) # 1. AFDX协议与ARINC664的背景介绍 ## 1.1 现代航空通信协议的发展 随着现代航空业的发展,对于飞机内部通信网络的要求也越来越高。传统的航

高级字符设备驱动技巧大公开:优化buffer管理与内存映射机制

![高级字符设备驱动技巧大公开:优化buffer管理与内存映射机制](https://img-blog.csdnimg.cn/direct/4077eef096ec419c9c8bc53986ebed01.png) 参考资源链接:[《Linux设备驱动开发详解》第二版-宋宝华-高清PDF](https://wenku.csdn.net/doc/70k3eb2aec?spm=1055.2635.3001.10343) # 1. 字符设备驱动概述 字符设备驱动是Linux内核中用于管理字符设备的软件组件。字符设备按字符而不是块的方式进行数据传输,这与块设备(如硬盘驱动器)相对,后者按数据块的方

【深度学习的交通预测力量】:构建上海轨道交通2030的智能预测模型

![【深度学习的交通预测力量】:构建上海轨道交通2030的智能预测模型](https://img-blog.csdnimg.cn/20190110103854677.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNjY4ODUxOQ==,size_16,color_FFFFFF,t_70) 参考资源链接:[上海轨道交通规划图2030版-高清](https://wenku.csdn.net/doc/647ff0fc

HEC-GeoHMS高级应用揭秘:实现自动化水文模拟的3种方法

参考资源链接:[HEC-GeoHMS操作详析:ArcGIS准备至流域处理全流程](https://wenku.csdn.net/doc/4o9gso36xa?spm=1055.2635.3001.10343) # 1. HEC-GeoHMS简介与核心概念 ## 1.1 概述 HEC-GeoHMS是一个基于地理信息系统(GIS)的强大工具,专门用于水文建模与分析。它将GIS数据与水文模拟无缝集成,为用户提供了一套全面的解决方案,用于处理水文过程的建模与模拟。HEC-GeoHMS是美国陆军工程兵团水文工程中心(HEC)研发的HEC系列软件的一部分,特别是在HEC-HMS(Hydrologic M

MIPI CSI-2核心概念大公开:规范书深度解读

参考资源链接:[mipi-CSI-2-标准规格书.pdf](https://wenku.csdn.net/doc/64701608d12cbe7ec3f6856a?spm=1055.2635.3001.10343) # 1. MIPI CSI-2技术概述 ## 1.1 MIPI CSI-2技术简介 MIPI CSI-2(Mobile Industry Processor Interface Camera Serial Interface version 2)是一种广泛应用于移动设备和高端成像系统中的数据传输协议。它为移动和嵌入式系统中的摄像头模块和处理器之间的高速串行接口提供标准化解决方案。

【Android虚拟设备管理终极攻略】:彻底解决SDK Emulator目录丢失问题

![【Android虚拟设备管理终极攻略】:彻底解决SDK Emulator目录丢失问题](https://android-ios-data-recovery.com/wp-content/uploads/2019/08/recover-files-from-androooid-1024x589.jpg) 参考资源链接:[Android Studio SDK下载问题:代理设置修复教程](https://wenku.csdn.net/doc/6401abcccce7214c316e988d?spm=1055.2635.3001.10343) # 1. Android虚拟设备管理概述 Andr