-
2022-01-06 21:02:36
欢迎大家评论区交流
import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; public class ConcurrentTestUtils { /** * @param run 要测试的代码 * @param threadSize 每次并发的线程数 * @param loop 测试次数 * @Author AmazingMud */ public static void execute(Runnable run,int threadSize,int loop){ AtomicInteger count = new AtomicInteger(); for (int j = 0; j <loop ; j++) { System.out.println("第"+(j+1)+"轮并发测试,每轮并发数"+threadSize); CountDownLatch countDownLatch = new CountDownLatch(1); Set<Thread> threads = new HashSet<>(threadSize); //批量新建线程 for (int i = 0; i <threadSize ; i++) { threads.add( new Thread(()->{ try { countDownLatch.await(); run.run(); } catch (InterruptedException e) { e.printStackTrace(); } },"Thread"+count.getAndIncrement())); } //开启所有线程并确保其进入Waiting状态 for (Thread thread : threads) { thread.start(); while(thread.getState() != Thread.State.WAITING); } //唤醒所有在countDownLatch上等待的线程 countDownLatch.countDown(); //等待所有线程执行完毕,开启下一轮 for (Thread thread : threads) { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void execute(Runnable run){ execute(run,1000,1); } public static void execute(Runnable run,int threadSize){ execute(run,threadSize,1); } public static void main(String[] args) { execute(()->{ System.out.println(Thread.currentThread()); },10,10); } }
更多相关内容 -
Java多线程并发执行demo代码实例
2020-08-18 23:50:22主要介绍了Java多线程并发执行demo代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
php多线程并发实现方法
2020-10-21 09:58:27主要介绍了php多线程并发实现方法,结合实例形式分析了php模拟多线程并发的相关操作技巧,以及在Linux平台借助shell实现多线程并发的操作方法,需要的朋友可以参考下 -
使用java的HttpClient实现多线程并发
2020-09-01 15:52:23主要介绍了使用java的HttpClient实现多线程并发的相关资料,需要的朋友可以参考下 -
基于Qt的多线程并发服务器
2018-08-03 13:50:11基于Qt的多线程并发服务器 incomingConnection(qintptr socketDescriptor)检测 -
WEBAPI多线程并发测试工具
2018-09-18 11:11:01WEB API 多线程并发测试工具; WEB API 多线程并发测试工具 -
qtconcurrent 多线程并发处理
2018-04-25 12:49:42qtconcurrent 多线程并发处理应用demo。开发环境Qt5.9.4 -
java 多线程并发实例
2014-03-11 13:30:52代码里面包含一个并发4个线程同时运行 全部开始 全部停止 单个停止还有点问题。 还有生产者消费者 里面的里面能帮助你理解多线程的运用! -
Windows下基于socket多线程并发通信的实现
2015-04-07 15:06:06本文介绍了在Windows 操作系统下基于TCP/IP 协议Socket 套接口的通信机制以及多线程编程知识与技巧,并给出多线程方式实现多用户与服务端(C/S)并发通信模型的详细算法,最后展现了用C++编写的多用户与服务器通信的... -
C++多线程并发(一)--- 线程创建与管理
2020-03-16 22:21:32一、何为并发 简单来说,并发指的是两个或多个独立的活动在同一时段内交替发生。与并发相近的另一个概念是并行,并行则强调的是多个独立的活动在同一时刻点同时发生。 二、为什么使用并发 在应用程序中使用并发的...文章目录
前言
我们都听说过摩尔定律:预计18个月会将芯片的性能提高一倍。早期的计算机时钟频率较低,比如1985年intel 80386 工作频率只有20 MHZ,提升CPU 核心的时钟频率带来的性能收益更大,到2006年Intel Core 2 处理器已经能够达到3.5 GHZ 的工作频率了。从2007年开始,CPU 时钟频率的提升就变得缓慢了,主要因为CPU 的功耗随时钟频率呈幂律增长,需要根据散热技术和制程工艺在性能与功耗间寻求平衡,既然CPU 时钟频率提升有限了,怎么继续按照摩尔定律提升性能呢?
CPU 是用来处理计算任务的,想要在单位时间内处理更多的计算任务,除了提升单核心的时钟频率让其计算的更快之外,还可以增加CPU 核心数,让多个CPU 核心协同计算,CPU 开始往多核心方向发展,到2019年AMD EPYC 2 代已经达到64核心128线程了。为了充分发挥多核心CPU 的性能,操作系统和编程语言对并发执行的支持越来越好,各种编程语言也陆续提供了并发编程的函数库,比如C++11 就新增了并发编程的线程支持库。我们想要让多核CPU 更好的发挥性能,更高效的为我们的程序服务,掌握并发编程思想还是很有必要的。
一、何为并发
刚开始接触计算机编程语言时,我们编写一个程序,在main入口函数中调用其它的函数,计算机按我们设定的调用逻辑来执行指令获得结果。如果我们想在程序中完成多个任务,可以将每个任务实现为一个函数然后根据业务逻辑逐个调用。但如果我们想让多个任务几乎同时执行(时间间隔很小,我们感觉是同时执行的一样),比如一边放歌一边显示歌词,恐怕实现起来就会有明显的顿挫感(比如先播放一句歌声,然后显示一行歌词),影响交互体验。
随着我们对计算性能的要求越来越高,多核心处理器很快普及流行。如果我们想让自己开发的程序更高效的运行,自然要充分发挥多核心处理器的优势。在多核心处理器上同时运行多个任务,比在单核心处理器上顺序执行多个任务高效的多。像单片机这种单核心处理器,在任务较多或者多个任务需要几乎同时执行时,也需要应用多任务并发编程提高对包括处理器在内的各硬件资源的利用效率。
1.1 并发与并行
说了这么多,那什么是并发呢?简单来说,并发指的是两个或多个独立的活动在同一时段内发生。并发在生活中随处可见:比如在跑步的时候同时听音乐,在看电脑显示器的同时敲击键盘等。
与并发相近的另一个概念是并行。它们两者存在很大的差别,图示如下:
- 并发:同一时间段内可以交替处理多个操作,强调同一时段内交替发生。
- 并行:同一时刻内同时处理多个操作,强调同一时刻点同时发生。
1.2 硬件并发与任务切换
既然并发是在同一时间段内交替发生即可,不要求同时发生,像单片机上的单核处理器也是可以支持并发多任务处理的,所以有单片机上跑的RTOS(Real-time operating system)诞生。单核心处理器上的多任务并发是靠任务切换实现的,跟多核处理器上的并行多任务处理还是有较大区别的,但对处理器的使用和多任务调度工作主要由操作系统完成了,所以我们在两者之间编写应用程序区别倒是不大。下面再贴个直观的图示:
- 双核处理器并行执行(硬件并发)对比单核处理器并发执行(任务上下文切换)
- 双核处理器均并发执行(一般任务数远大于处理器核心数,多核并发更常见)
1.3 多线程并发与多进程并发
前面一直在聊多任务并发,但计算机术语中用得更多的是线程与进程,三者的主要区别如下:
- 任务:从我们认知角度抽象出来的一个概念,放到计算机上主要指由软件完成的一个活动。一个任务既可以是一个进程,也可以是一个线程。简而言之,它指的是一系列共同达到某一目的的操作。例如,读取数据并将数据放入内存中。这个任务可以作为一个进程来实现,也可以作为一个线程(或作为一个中断任务)来实现。
- 进程:资源分配的基本单位,也可能作为调度运行的单位。可以把一个进程看成是一个独立的程序,在内存中有其完备的数据空间和代码空间。一个进程所拥有的数据和变量只属于它自己。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放人进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才真正运行。所以,进程是系统中的并发执行的单位。
- 线程:执行处理器调度的基本单位。一个进程由一个或多个线程构成,各线程共享相同的代码和全局数据,但各有其自己的堆栈。由于堆栈是每个线程一个,所以局部变量对每一线程来说是私有的。由于所有线程共享同样的代码和全局数据,它们比进程更紧密,比单独的进程间更趋向于相互作用,线程间的相互作用更容易些,因为它们本身就有某些供通信用的共享内存:进程的全局数据。
由上面的定义可以看出,一个进程和一个线程最显著的区别是:线程有自己的全局数据。线程存在于进程中,因此一个进程的全局变量由所有的线程共享。由于线程共享同样的系统区域,操作系统分配给一个进程的资源对该进程的所有线程都是可用的,正如全局数据可供所有线程使用一样。
在Mac、Windows NT等采用微内核结构的操作系统中,进程的功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统中,真正调度运行的基本单位是线程。因此,实现并发功能的单位是线程。在Linux系统中,线程的实现和进程并不特别区分,线程只不过是一种特殊的进程。多进程并发编程与多线程并发编程的区别主要在有没有共享数据,多进程间的通信较复杂且代价较大,主要的进程间通信渠道有管道、信号、文件、套接字等。由于C++没有提供进程间通信的原生支持,后续主要介绍多线程并发编程,和多线程间的同步与通信。
二、如何使用并发
2.1 为什么使用并发
在应用程序中使用并发的原因主要有两个:关注点分离和性能。事实上,我甚至可以说它们差不多是使用并发的唯一原因;当你观察的足够仔细时,一切其他因素都可以归结到这两者之一(或者可能是二者兼有)。
- 关注点分离:通过将相关的代码放在一起并将无关的代码分开,可以使你的程序更容易理解和测试,从而减少出错的可能性。你可以使用并发来分隔不同的功能区域,即使在这些不同功能区域的操作需要在同一时刻发生的情况下;若不显式地使用并发,你要么被迫编写任务切换框架,要么在操作中主动地调用不相关的一段代码。
- 更高效的性能:为了充分发挥多核心处理器的优势,使用并发将单个任务分成几部分且各自并行运行,从而降低总运行时间。根据任务分割方式的不同,又可以将其分为两大类:一类是对同样的数据应用不同的处理算法(任务并行);另一类是用同样的处理算法共同处理数据的几部分(数据并行)。
知道何时不使用并发与知道何时使用它一样重要。基本上,不使用并发的唯一原因就是在收益比不上成本的时候。使用并发的代码在很多情况下难以理解,因此编写和维护的多线程代码就有直接的脑力成本,同时额外的复杂性也可能导致更多的错误。除非潜在的性能增益足够大或关注点分离地足够清晰,能抵消确保其正确所需的额外的开发时间以及与维护多线程代码相关的额外成本,否则不要使用并发。
2.2 在C++中使用并发和多线程
在早期的C++标准中,比如1998 C++标准版不承认线程的存在,并且各种语言要素的操作效果都以顺序抽象机的形式编写。不仅如此,内存模型也没有被正式定义,所以对于1998 C++标准,你没办法在缺少编译器相关扩展的情况下编写多线程应用程序。如果在之前想使用多线程并发编程,可以借助编译器厂商提供的平台相关的扩展多线程支持API(比如POSIX C和Microsoft Windows API),但这种多线程支持对平台依赖度较高,导致可移植性较差。
为了解决平台相关多线程API使用上的问题,逐渐开发出了Boost、ACE等平台无关的多线程支持类库。直到C++11标准的发布,借鉴了很多Boost类库的经验,将多线程支持纳入C++标准库。C++11标准不仅提供了一个全新的线程感知内存模型,也包含了用于管理线程、保护共享数据、线程间同步操作以及低级原子操作的各个类。
对于C++整体以及包含低级工具的C++类——特别是在新版C++线程库里的那些,参与高性能计算的开发者常常关注的一点就是效率。如果你正寻求极致的性能,那么理解与直接使用底层的低级工具相比,使用高级工具所带来的实现成本,是很重要的。这个成本就是抽象惩罚(abstraction penalty)。标准C++线程库在设计时,就非常注重高效的性能,提供了足够的低级工具(比如原子操作库),以付出尽可能低的抽象惩罚。C++标准库也提供了更高级别的抽象和工具,它们使得编写多线程代码更简单和不易出错。有时候运用这些工具确实会带来性能成本,因为必须执行额外的代码。但是这种性能成本并不一定意味着更高的抽象惩罚;总体来看,这种性能成本并不比通过手工编写等效的函数而招致的成本更高,同时编译器可能会很好地内联大部分额外的代码。
三、C++线程创建
一个多线程C++程序是什么样子的?它看上去和其他所有C++程序一样,通常是变量、类以及函数的组合。唯一真正的区别在于某些函数可以并发运行,所以你需要确保共享数据的并发访问是安全的。当然,为了并发地运行函数,必须使用特定的函数以及对象来管理各个线程。
3.1 C++11新标准多线程支持库
- < thread > : 提供线程创建及管理的函数或类接口;
- < mutex > : 为线程提供获得独占式资源访问能力的互斥算法,保证多个线程对共享资源的同步访问;
- < condition_variable > : 允许一定量的线程等待(可以定时)被另一线程唤醒,然后再继续执行;
- < future > : 提供了一些工具来获取异步任务(即在单独的线程中启动的函数)的返回值,并捕捉其所抛出的异常;
- < atomic > : 为细粒度的原子操作(不能被处理器拆分处理的操作)提供组件,允许无锁并发编程。
3.2 线程创建的简单示例
线程创建和管理的函数或类主要由< thread >库文件来提供,该库文件的主要操作如下:
由上表可知,通过std::thread t(f, args…)创建线程,可以给线程函数传递参数。通过join()函数关联并阻塞线程,等待该线程执行完毕后继续;通过detach()函数解除关联使线程可以与主线程并发执行,但若主线程执行完毕退出后,detach()接触关联的线程即便没有执行完毕,也将自动退出,有时可能这并非我们预期的结果,所以需要特别注意。下面给出一段线程管理的示例代码://thread1.cpp 创建线程,并观察线程的并发执行与阻塞等待 #include <iostream> #include <thread> #include <chrono> using namespace std; void thread_function(int n) { std::thread::id this_id = std::this_thread::get_id(); //获取线程ID for(int i = 0; i < 5; i++){ cout << "Child function thread " << this_id<< " running : " << i+1 << endl; std::this_thread::sleep_for(std::chrono::seconds(n)); //进程睡眠n秒 } } class Thread_functor { public: // functor行为类似函数,C++中的仿函数是通过在类中重载()运算符实现,使你可以像使用函数一样来创建类的对象 void operator()(int n) { std::thread::id this_id = std::this_thread::get_id(); for(int i = 0; i < 5; i++){ cout << "Child functor thread " << this_id << " running: " << i+1 << endl; std::this_thread::sleep_for(std::chrono::seconds(n)); //进程睡眠n秒 } } }; int main() { thread mythread1(thread_function, 1); // 传递初始函数作为线程的参数 if(mythread1.joinable()) //判断是否可以成功使用join()或者detach(),返回true则可以,返回false则不可以 mythread1.join(); // 使用join()函数阻塞主线程直至子线程执行完毕 Thread_functor thread_functor; //函数对象实例化一个对象 thread mythread2(thread_functor, 3); // 传递初始函数作为线程的参数 if(mythread2.joinable()) mythread2.detach(); // 使用detach()函数让子线程和主线程并行运行,主线程也不再等待子线程 auto thread_lambda = [](int n){ //lambda表达式格式:[capture list] (params list) mutable exception-> return type { function body } std::thread::id this_id = std::this_thread::get_id(); for(int i = 0; i < 5; i++) { cout << "Child lambda thread " << this_id << " running: " << i+1 << endl; std::this_thread::sleep_for(std::chrono::seconds(n)); //进程睡眠n秒 } }; thread mythread3(thread_lambda, 4); // 传递初始函数作为线程的参数 if(mythread3.joinable()) mythread3.join(); // 使用join()函数阻塞主线程直至子线程执行完毕 std::thread::id this_id = std::this_thread::get_id(); for(int i = 0; i < 5; i++){ cout << "Main thread " << this_id << " running: " << i+1 << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } getchar(); return 0; }
使用GCC编译为可执行程序的命令如下:
g++ -Wall -g -std=c++11 -pthread thread1.cpp -o thread1 # -Wall显示所有警告,-g输出调试信息,-std=c++11使用c++11标准编译,-pthread编译使用POSIX thread库文件
线程创建的参数是函数对象,函数对象不止是函数指针或成员函数指针,同时还包括函数对象(仿函数)与lambda表达式。上面的代码分别用三种函数对象创建了三个线程,其中第一个线程mythread1阻塞等待其执行完后继续往下执行,第二个线程mythread2不阻塞等待在后台与后面的第三个线程mythread3并发执行,第三个线程继续阻塞等待其完成后再继续往下执行主线程任务。
为了便于观察并发过程,对三个线程均用了睡眠延时this_thread::sleep_for(duration)函数,且延时时间作为参数传递给该函数。这里的参数是支持C++泛型模板的,STL标准容器类型(比如Array/Vector/Deque/List/Set/Map/String等)都可以作为参数传递,但这里的参数默认是以拷贝的方式传递参数的,当期望传入一个引用时,要使用std::ref进行转换。
针对任何线程(包括主线程),< thread > 还声明了一个命名空间std::this_thread,用以提高线程专属的全局函数。函数声明和效果见下表:
上面的代码就是利用了std::this_thread提供的函数获得当前线程的ID,让当前线程睡眠一段时间(一般需要< chrono >头文件提供duration或timepoint)的功能,代码执行结果如下图所示:
上面的示例假如多重复运行几次,有很大可能会出现某行与其他行交叠错乱的情况(如下图所示),为何会出现这种情况呢?这就涉及到多线程资源竞争的问题了,即一个线程对某一资源(这里指显示终端)的访问还未完成,另一线程抢夺并访问了该资源,导致该资源数据混乱情况的出现。解决方案详见下一篇文章:C++多线程并发(二)—线程同步
更多文章:
- 并发:同一时间段内可以交替处理多个操作,强调同一时段内交替发生。
-
java多线程并发编程例子
2012-03-08 12:11:38关于java.util.concurrent多线程核心包内各种线程资源的使用场景例子 -
C++多线程并发中线程管理
2021-12-02 19:27:40一、何为并发 刚开始接触计算机编程语言时,我们编写一个程序,在main入口函数中调用其它的函数,计算机按我们设定的调用逻辑来执行指令获得结果。如果我们想在程序中完成多个任务,可以将每个任务实现为一个函数...一、何为并发
刚开始接触计算机编程语言时,我们编写一个程序,在main入口函数中调用其它的函数,计算机按我们设定的调用逻辑来执行指令获得结果。如果我们想在程序中完成多个任务,可以将每个任务实现为一个函数然后根据业务逻辑逐个调用。但如果我们想让多个任务几乎同时执行(时间间隔很小,我们感觉是同时执行的一样),比如一边放歌一边显示歌词,恐怕实现起来就会有明显的顿挫感(比如先播放一句歌声,然后显示一行歌词),影响交互体验。
随着我们对计算性能的要求越来越高,多核心处理器很快普及流行。如果我们想让自己开发的程序更高效的运行,自然要充分发挥多核心处理器的优势。在多核心处理器上同时运行多个任务,比在单核心处理器上顺序执行多个任务高效的多。像单片机这种单核心处理器,在任务较多或者多个任务需要几乎同时执行时,也需要应用多任务并发编程提高对包括处理器在内的各硬件资源的利用效率。
1.1 并发与并行
说了这么多,那什么是并发呢?简单来说,并发指的是两个或多个独立的活动在同一时段内发生。并发在生活中随处可见:比如在跑步的时候同时听音乐,在看电脑显示器的同时敲击键盘等。
与并发相近的另一个概念是并行。它们两者存在很大的差别,图示如下:
- 并发:同一时间段内可以交替处理多个操作,强调同一时段内交替发生。
- 并行:同一时刻内同时处理多个操作,强调同一时刻点同时发生。
1.2 硬件并发与任务切换
既然并发是在同一时间段内交替发生即可,不要求同时发生,像单片机上的单核处理器也是可以支持并发多任务处理的,所以有单片机上跑的RTOS(Real-time operating system)诞生。单核心处理器上的多任务并发是靠任务切换实现的,跟多核处理器上的并行多任务处理还是有较大区别的,但对处理器的使用和多任务调度工作主要由操作系统完成了,所以我们在两者之间编写应用程序区别倒是不大。下面再贴个直观的图示:
- 双核处理器并行执行(硬件并发)对比单核处理器并发执行(任务上下文切换)
- 双核处理器均并发执行(一般任务数远大于处理器核心数,多核并发更常见)
1.3 多线程并发与多进程并发
前面一直在聊多任务并发,但计算机术语中用得更多的是线程与进程,三者的主要区别如下:
- 任务:从我们认知角度抽象出来的一个概念,放到计算机上主要指由软件完成的一个活动。一个任务既可以是一个进程,也可以是一个线程。简而言之,它指的是一系列共同达到某一目的的操作。例如,读取数据并将数据放入内存中。这个任务可以作为一个进程来实现,也可以作为一个线程(或作为一个中断任务)来实现。
- 进程:资源分配的基本单位,也可能作为调度运行的单位。可以把一个进程看成是一个独立的程序,在内存中有其完备的数据空间和代码空间。一个进程所拥有的数据和变量只属于它自己。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放人进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才真正运行。所以,进程是系统中的并发执行的单位。
- 线程:执行处理器调度的基本单位。一个进程由一个或多个线程构成,各线程共享相同的代码和全局数据,但各有其自己的堆栈。由于堆栈是每个线程一个,所以局部变量对每一线程来说是私有的。由于所有线程共享同样的代码和全局数据,它们比进程更紧密,比单独的进程间更趋向于相互作用,线程间的相互作用更容易些,因为它们本身就有某些供通信用的共享内存:进程的全局数据。
由上面的定义可以看出,一个进程和一个线程最显著的区别是:线程有自己的全局数据。线程存在于进程中,因此一个进程的全局变量由所有的线程共享。由于线程共享同样的系统区域,操作系统分配给一个进程的资源对该进程的所有线程都是可用的,正如全局数据可供所有线程使用一样。
在Mac、Windows NT等采用微内核结构的操作系统中,进程的功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统中,真正调度运行的基本单位是线程。因此,实现并发功能的单位是线程。在Linux系统中,线程的实现和进程并不特别区分,线程只不过是一种特殊的进程。多进程并发编程与多线程并发编程的区别主要在有没有共享数据,多进程间的通信较复杂且代价较大,主要的进程间通信渠道有管道、信号、文件、套接字等。由于C++没有提供进程间通信的原生支持,后续主要介绍多线程并发编程,和多线程间的同步与通信。
二、如何使用并发
2.1 为什么使用并发
在应用程序中使用并发的原因主要有两个:关注点分离和性能。事实上,我甚至可以说它们差不多是使用并发的唯一原因;当你观察的足够仔细时,一切其他因素都可以归结到这两者之一(或者可能是二者兼有)。
- 关注点分离:通过将相关的代码放在一起并将无关的代码分开,可以使你的程序更容易理解和测试,从而减少出错的可能性。你可以使用并发来分隔不同的功能区域,即使在这些不同功能区域的操作需要在同一时刻发生的情况下;若不显式地使用并发,你要么被迫编写任务切换框架,要么在操作中主动地调用不相关的一段代码。
- 更高效的性能:为了充分发挥多核心处理器的优势,使用并发将单个任务分成几部分且各自并行运行,从而降低总运行时间。根据任务分割方式的不同,又可以将其分为两大类:一类是对同样的数据应用不同的处理算法(任务并行);另一类是用同样的处理算法共同处理数据的几部分(数据并行)。
知道何时不使用并发与知道何时使用它一样重要。基本上,不使用并发的唯一原因就是在收益比不上成本的时候。使用并发的代码在很多情况下难以理解,因此编写和维护的多线程代码就有直接的脑力成本,同时额外的复杂性也可能导致更多的错误。除非潜在的性能增益足够大或关注点分离地足够清晰,能抵消确保其正确所需的额外的开发时间以及与维护多线程代码相关的额外成本,否则不要使用并发。
2.2 在C++中使用并发和多线程
在早期的C++标准中,比如1998 C++标准版不承认线程的存在,并且各种语言要素的操作效果都以顺序抽象机的形式编写。不仅如此,内存模型也没有被正式定义,所以对于1998 C++标准,你没办法在缺少编译器相关扩展的情况下编写多线程应用程序。如果在之前想使用多线程并发编程,可以借助编译器厂商提供的平台相关的扩展多线程支持API(比如POSIX C和Microsoft Windows API),但这种多线程支持对平台依赖度较高,导致可移植性较差。
为了解决平台相关多线程API使用上的问题,逐渐开发出了Boost、ACE等平台无关的多线程支持类库。直到C++11标准的发布,借鉴了很多Boost类库的经验,将多线程支持纳入C++标准库。C++11标准不仅提供了一个全新的线程感知内存模型,也包含了用于管理线程、保护共享数据、线程间同步操作以及低级原子操作的各个类。
对于C++整体以及包含低级工具的C++类——特别是在新版C++线程库里的那些,参与高性能计算的开发者常常关注的一点就是效率。如果你正寻求极致的性能,那么理解与直接使用底层的低级工具相比,使用高级工具所带来的实现成本,是很重要的。这个成本就是抽象惩罚(abstraction penalty)。标准C++线程库在设计时,就非常注重高效的性能,提供了足够的低级工具(比如原子操作库),以付出尽可能低的抽象惩罚。C++标准库也提供了更高级别的抽象和工具,它们使得编写多线程代码更简单和不易出错。有时候运用这些工具确实会带来性能成本,因为必须执行额外的代码。但是这种性能成本并不一定意味着更高的抽象惩罚;总体来看,这种性能成本并不比通过手工编写等效的函数而招致的成本更高,同时编译器可能会很好地内联大部分额外的代码。
三、C++线程创建
一个多线程C++程序是什么样子的?它看上去和其他所有C++程序一样,通常是变量、类以及函数的组合。唯一真正的区别在于某些函数可以并发运行,所以你需要确保共享数据的并发访问是安全的。当然,为了并发地运行函数,必须使用特定的函数以及对象来管理各个线程。
3.1 C++11新标准多线程支持库
- < thread > :包含std::thread类以及std::this_thread命名空间。管理线程的函数和类的声明;
- < atomic > :包含std::atomic和std::atomic_flag类,以及一套C风格的原子类型和与C兼容的原子操作的函数;
- < mutex > :包含了与互斥量相关的类以及其他类型和函数;
- < future > :包含两个Provider类(std::promise和std::package_task)和两个Future类(std::future和std::shared_future)以及相关的类型和函数;
- < condition_variable > :包含与条件变量相关的类,包括std::condition_variable和std::condition_variable_any。
3.2 线程创建的简单示例
线程创建和管理的函数或类主要由< thread >库文件来提供,该库文件的主要操作如下:
由上表可知,通过std::thread t(f, args…)创建线程,可以给线程函数传递参数。通过join()函数关联并阻塞线程,等待该线程执行完毕后继续;通过detach()函数解除关联使线程可以与主线程并发执行,但若主线程执行完毕退出后,detach()接触关联的线程即便没有执行完毕,也将自动退出,有时可能这并非我们预期的结果,所以需要特别注意。下面给出一段线程管理的示例代码://thread1.cpp 创建线程,并观察线程的并发执行与阻塞等待
#include <iostream>
#include <thread>
#include <chrono>using namespace std;
void thread_function(int n)
{
std::thread::id this_id = std::this_thread::get_id(); //获取线程ID<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">5</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span> cout <span class="token operator"><<</span> <span class="token string">"Child function thread "</span> <span class="token operator"><<</span> this_id<span class="token operator"><<</span> <span class="token string">" running : "</span> <span class="token operator"><<</span> i<span class="token operator">+</span><span class="token number">1</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span> std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">sleep_for</span><span class="token punctuation">(</span>std<span class="token operator">::</span>chrono<span class="token operator">::</span><span class="token function">seconds</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//进程睡眠n秒</span> <span class="token punctuation">}</span>
}
class Thread_functor
{
public:
// functor行为类似函数,C++中的仿函数是通过在类中重载()运算符实现,使你可以像使用函数一样来创建类的对象
void operator()(int n)
{
std::thread::id this_id = std::this_thread::get_id();<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">5</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span> cout <span class="token operator"><<</span> <span class="token string">"Child functor thread "</span> <span class="token operator"><<</span> this_id <span class="token operator"><<</span> <span class="token string">" running: "</span> <span class="token operator"><<</span> i<span class="token operator">+</span><span class="token number">1</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span> std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">sleep_for</span><span class="token punctuation">(</span>std<span class="token operator">::</span>chrono<span class="token operator">::</span><span class="token function">seconds</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//进程睡眠n秒</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
};
int main()
{
thread mythread1(thread_function, 1); // 传递初始函数作为线程的参数
if(mythread1.joinable()) //判断是否可以成功使用join()或者detach(),返回true则可以,返回false则不可以
mythread1.join(); // 使用join()函数阻塞主线程直至子线程执行完毕Thread_functor thread_functor<span class="token punctuation">;</span> <span class="token comment">//函数对象实例化一个对象</span> thread <span class="token function">mythread2</span><span class="token punctuation">(</span>thread_functor<span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 传递初始函数作为线程的参数</span> <span class="token keyword">if</span><span class="token punctuation">(</span>mythread2<span class="token punctuation">.</span><span class="token function">joinable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> mythread2<span class="token punctuation">.</span><span class="token function">detach</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 使用detach()函数让子线程和主线程并行运行,主线程也不再等待子线程</span> <span class="token keyword">auto</span> thread_lambda <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment">//lambda表达式格式:[capture list] (params list) mutable exception-> return type { function body }</span> std<span class="token operator">::</span>thread<span class="token operator">::</span>id this_id <span class="token operator">=</span> std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">get_id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">5</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator"><<</span> <span class="token string">"Child lambda thread "</span> <span class="token operator"><<</span> this_id <span class="token operator"><<</span> <span class="token string">" running: "</span> <span class="token operator"><<</span> i<span class="token operator">+</span><span class="token number">1</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span> std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">sleep_for</span><span class="token punctuation">(</span>std<span class="token operator">::</span>chrono<span class="token operator">::</span><span class="token function">seconds</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//进程睡眠n秒</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> thread <span class="token function">mythread3</span><span class="token punctuation">(</span>thread_lambda<span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 传递初始函数作为线程的参数</span> <span class="token keyword">if</span><span class="token punctuation">(</span>mythread3<span class="token punctuation">.</span><span class="token function">joinable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> mythread3<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 使用join()函数阻塞主线程直至子线程执行完毕</span> std<span class="token operator">::</span>thread<span class="token operator">::</span>id this_id <span class="token operator">=</span> std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">get_id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">5</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span> cout <span class="token operator"><<</span> <span class="token string">"Main thread "</span> <span class="token operator"><<</span> this_id <span class="token operator"><<</span> <span class="token string">" running: "</span> <span class="token operator"><<</span> i<span class="token operator">+</span><span class="token number">1</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span> std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">sleep_for</span><span class="token punctuation">(</span>std<span class="token operator">::</span>chrono<span class="token operator">::</span><span class="token function">seconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">getchar</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
使用GCC编译为可执行程序的命令如下:
g++ -Wall -g -std=c++11 -pthread thread1.cpp -o thread1 # -Wall显示所有警告,-g输出调试信息,-std=c++11使用c++11标准编译,-pthread编译使用POSIX thread库文件
线程创建的参数是函数对象,函数对象不止是函数指针或成员函数指针,同时还包括函数对象(仿函数)与lambda表达式。上面的代码分别用三种函数对象创建了三个线程,其中第一个线程mythread1阻塞等待其执行完后继续往下执行,第二个线程mythread2不阻塞等待在后台与后面的第三个线程mythread3并发执行,第三个线程继续阻塞等待其完成后再继续往下执行主线程任务。
为了便于观察并发过程,对三个线程均用了睡眠延时this_thread::sleep_for(duration)函数,且延时时间作为参数传递给该函数。这里的参数是支持C++泛型模板的,STL标准容器类型(比如Array/Vector/Deque/List/Set/Map/String等)都可以作为参数传递,但这里的参数默认是以拷贝的方式传递参数的,当期望传入一个引用时,要使用std::ref进行转换。
针对任何线程(包括主线程),< thread > 还声明了一个命名空间std::this_thread,用以提高线程专属的全局函数。函数声明和效果见下表:
上面的代码就是利用了std::this_thread提供的函数获得当前线程的ID,让当前线程睡眠一段时间(一般需要< chrono >头文件提供duration或timepoint)的功能,代码执行结果如下图所示:上面的示例假如多重复运行几次,有很大可能会出现某行与其他行交叠错乱的情况(如下图所示),为何会出现这种情况呢?这就涉及到多线程资源竞争的问题了,即一个线程对某一资源(这里指显示终端)的访问还未完成,另一线程抢夺并访问了该资源,导致该资源数据混乱情况的出现。解决方案详见下一篇文章:C++多线程并发(二)—线程同步
-
JAVA中的多线程并发运行安全问题
2021-03-19 17:58:27JAVA中的多线程并发运行安全问题 1.什么是多线程并发运行安全问题? 当多个线程并发操作一个数据时,由于线程操作的时间不可控的原因,可能会导致操作该数据时的过程没有按照程序设计的执行顺序运行,导致操作后数据...JAVA中的多线程并发运行安全问题
1.什么是多线程并发运行安全问题?
当多个线程并发操作一个数据时,由于线程操作的时间不可控的原因,可能会导致操作该数据时的过程没有按照程序设计的执行顺序运行,导致操作后数据出现混乱,严重时可导致系统瘫痪。
2.用synchronized修饰的方法
当一个方法用synchronized修饰,那么该方法变为“同步方法“多个线程不能同时进入方法内容运行的,必须时有顺序的一个一个运行,这样就能避免并发安全问题。
案例:抢豆豆事件(使豆豆的剩余量不能为负数)
public class SyncDemo { public static void main(String[] args) { Table table=new Table(); Thread t1=new Thread(){ public void run(){ while(true){ int n=table.getBean(); Thread.yield(); System.out.println(Thread.currentThread().getName()+",豆豆还剩"+n); } } }; Thread t2=new Thread(){ public void run(){ while(true){ int n=table.getBean(); Thread.yield(); System.out.println(Thread.currentThread().getName()+",豆豆还剩"+n); } } }; t1.start(); t2.start(); } } class Table{ private int bean=10; public synchronized int getBean(){ if(bean==0){ throw new RuntimeException("没有豆豆了"); } /* * yield()将导致线程从运行状态转到就绪状态,但是可能没有效果 * 作用时暂停当前正在执行的线程对象(放弃当前拥有的cpu资源), * 并执行其他线程。 */ Thread .yield();//模拟切换线程 return bean--; } }
运行结果:
当豆豆剩余量为0时,程序抛出异常。如果不加锁的话,程序有一定的几率会跳过豆豆为0,这个条件,而一直运行下去。
3.同步块
1.有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高效率。
2.同步块
synchronized(同步监视器){
需要同步运行的代码片段
}
3.同步块可以更灵活准确的锁定需要同步运行的代码片段,这样可以有效缩小同步范围提高并发效率,但是需要注意,必须保证多个线程看到同步监视器对象是同一个对象才可以。
案例:模拟商场买衣服
public class Syncdemo2 { public static void main(String[] args) { Shop shop=new Shop(); Thread t1=new Thread(){ public void run(){ shop.buy(); } }; Thread t2=new Thread(){ public void run(){ shop.buy(); } }; t1.start(); t2.start(); } } class Shop{ public void buy(){ try { String name=Thread.currentThread().getName(); System.out.println(name+"选衣服"); Thread.sleep(2000); synchronized (this) { System.out.println(name+"试衣服"); Thread.sleep(2000); } System.out.println(name+"结账走人"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
运行结果:
Thread-1选衣服
Thread-0选衣服
Thread-1试衣服
Thread-1结账走人
Thread-0试衣服
Thread-0结账走人分析:
两个线程不会同时在试衣服,因为在试衣服环节设置了同步块,这使线程在运行试衣服环节时变得有序。
4.使用Synchronized修饰静态方法
1.静态方法若使用了Synchronized修饰后,那么方法一定具有同步效果
2.静态方法的对象是当前类的对象
3.class类的每一个实例用于表达jvm加载一个类,当jvm加载一个类时后就会实例化一个class的实例用于表示它,每一个类在jvm都有且只有一个class的实例,所以静态方法锁的就是当前类对应的class的实例。
public class Syncdemo3 { public static void main(String[] args) { Thread t1=new Thread(){ public void run(){ Foo.dosome(); } }; Thread t2=new Thread(){ public void run(){ Foo.dosome(); } }; t1.start(); t2.start(); } } class Foo{ public synchronized static void dosome(){ try { String name=Thread.currentThread().getName(); System.out.println(name+"正在运行dosome方法"); Thread.sleep(3000); System.out.println(name+"运行结束》》"); } catch (Exception e) { e.printStackTrace(); } } }
5.互斥锁
使用synchronized锁定多段代码,而锁的对象相同时,这些代码片段之间就是互斥锁,多个线程不能同时执行这些方法.
public class Syncdemo4 { public static void main(String[] args){ Eoo eoo=new Eoo(); Thread t1=new Thread(){ public void run(){ eoo.test01(); } }; Thread t2=new Thread(){ public void run(){ eoo.test02(); } }; t1.start(); t2.start(); } } class Eoo{ public synchronized void test01(){ String name=Thread.currentThread().getName(); System.out.println(name+"正在运行1方法"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name+"1运行完毕"); } public synchronized void test02(){ String name=Thread.currentThread().getName(); System.out.println(name+"正在运行2方法"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name+"2运行完毕"); } }
运行结果:
Thread-0正在运行1方法
Thread-01运行完毕
Thread-1正在运行2方法
Thread-12运行完毕结果分析:
因为两个线程锁的对象相同,因此,当一个线程运行它的方法时,另一个线程不会一起运行运行它的方法,直到,一个线程运行结束后,他才可以进入这个类,来运行他的方法。
6.死锁现象:
线程都是保持着自己的锁,但是都是等待对方来释放锁,就出现互相”僵持“的情况,导致程序不会继续向后运行。
public class Syncdemo5 { public static void main(String[] args) { Poo p=new Poo(); Thread t1=new Thread(){ public void run(){ p.method1(); } }; Thread t2=new Thread(){ public void run(){ p.method2(); } }; t1.start(); t2.start(); } } class Poo{ Object A=new Object(); Object B=new Object(); public void method1(){ String name=Thread.currentThread().getName(); synchronized (A) { try { System.out.println(name+"A正在运行。。。"); Thread.sleep(3000); System.out.println(name+"A运行完毕"); method2(); } catch (Exception e) { e.printStackTrace(); } } } public void method2(){ String name=Thread.currentThread().getName(); synchronized (B) { try { System.out.println(name+"B正在运行。。。"); Thread.sleep(3000); System.out.println(name+"B运行完毕"); method1(); } catch (Exception e) { e.printStackTrace(); } } } }
运行结果:
Thread-0A正在运行。。。
Thread-1B正在运行。。。
Thread-0A运行完毕
Thread-1B运行完毕结果分析:
虽然有输出结果,但是,程序并没有运行结束,两个线程都等待着对方来释放锁,而僵持不下。
7.wait()和sleep()的区别
1.wait是object类中的方法,sleep是Thread中的方法
2.最主要的是sleep方法调用后,并没有释放锁,使得线程仍然可以同步控制,sleep不会让出出系统资源,sleep方法可以在任何地方使用,而wait必须在synchronized方法或者synchronized 块中使用,否则会抛出异常(java.lang.IllegalMonitorStateException),wait方法不仅让出cpu,还会释放已占有的同步资源;
3.sleep必须捕获异常,而wait,nofify和nofifyAll不需要捕获异常。
4.sleep是让某个线程暂时运行一段时间,其控制范围是由当前线程决定的,主动权在自己手里,而wait是由某个确定的对象来调用,主动权在某个对象手里。
public class WaitDemo { public static void main(String[] args) { Thread t1=new ThreadMy01(); Thread t2=new ThreadMy02(); t1.start(); t2.start(); } } class ThreadMy01 extends Thread{ public static StringBuilder str=new StringBuilder(); public void run(){ String name=Thread.currentThread().getName(); synchronized (str) { for(int i=0;i<5;i++){ try { str.wait(300);//完全释放锁 //Thread.sleep(300);//不释放锁 str.append('a'); System.out.println(name+str); } catch (InterruptedException e) { e.printStackTrace(); } } } } } //唤醒wait状态 class ThreadMy02 extends Thread{ public void run(){ synchronized (ThreadMy01.str) { for(int i=0;i<2;i++){ try { Thread.sleep(2000); System.out.println("888"); } catch (Exception e) { e.printStackTrace(); } } //唤醒wait()状态 ThreadMy01.str.notify(); } } }
运行结果:
888
888
Thread-0a
Thread-0aa
Thread-0aaa
Thread-0aaaa
Thread-0aaaaa结果分析:
在运行以上代码时sleep虽然睡眠2秒,但是wait并没有 执行,说明sleep不会让出系统资源。
线程的生命周期:
-
Java 模拟线程并发
2010-04-28 23:18:18Java 模拟线程并发 Java, 模拟线程并发,线程,并发 Java, 模拟线程并发,线程,并发 Java, 模拟线程并发,线程,并发 Java, 模拟线程并发,线程,并发 -
java 多线程并发查询mysql数据
2020-09-29 17:24:01程序猿学社的GitHub,欢迎Star github技术专题 本文已记录到github 文章目录前言需求思路代码 前言 用过mysql的朋友,对mysql性能应该有一定的感悟,数据...使用多线程并发查询每一天的数据在合并。 代码 /** . -
并发并行多线程并发问题线程安全问题
2022-02-24 03:11:152.并发(一个线程也可以,指的是指的是 一个线程或多个线程上,多个程序之间的多路复用,即看起来是同时) redis就是这种技术,单线程+多路IO复用 3.我们通常说的并发,就是指的 并行, 4.单线程处理多个请求,指的... -
C++多线程并发(二)---线程同步之互斥锁
2019-03-20 00:08:29在前一篇文章《C++多线程并发编程(一)—线程管理》中解释多线程并发时说到两个比较重要的概念: 多线程并发:在同一时间段内交替处理多个操作,线程切换时间片是很短的(一般为毫秒级),一个时间片多数时候... -
多线程并发和并行的区别
2020-09-13 14:35:34背景 对于java开发从业人员来说,并发编程是绕不开的话题,juc并发包下提供了一系列多线程场景解决方案。 随着jdk1.8的普及,多线程处理... 多线程并发和并行的区别 2. parallelStream()并行滥用的后果 3 -
Java多线程并发编程知识体系(附大图-持续更新)
2021-08-22 11:34:05Java并发编程 并发编程的优势 提升CPU资源利用率 CPU缓存 操作系统分时复用 指令流水线优化 提升吞吐量 提升程序响应速度 更好的编程模型 并发带来的问题 安全性问题 定义:多线程读写共享变量时出现不正确的... -
高并发之——多线程并发与线程安全总结
2019-10-26 23:05:08可以参考如下博文: 《Java之——内存模型》 《高并发之——如何安全的发布对象(含各种单例代码分析)》 《高并发之——线程安全性》 《高并发之——线程安全策略》 ...《高并发之——多线程并发扩展》 ... -
Java多线程并发中支持并发的list对象
2021-03-08 16:59:50Java多线程并发编程中并发容器第二篇之List的并发类讲解概述本文我们将详细讲解list对应的并发容器以及用代码来测试ArrayList、vector以及CopyOnWriteArrayList在100个线程向list中添加1000个数据后的比较本文是... -
C++多线程并发(三)---线程同步之条件变量
2019-05-03 12:43:12在前一篇文章《C++多线程并发编程(二)—线程同步之互斥锁》中解释了线程同步的原理和实现,使用互斥锁解决数据竞争访问问题,算是线程同步的加锁原语,用于排他性的访问共享数据。我们在使用mutex时,一般都会期望... -
实现 Java 多线程并发控制框架
2019-01-08 04:46:252006 年 8 月 14 日 Java 提供了语言级别的线程支持,所以在 Java 中使用多线程相对于 C,C++ 来说更简单便捷,但...在本文中,我们将讨论如何实现一个 Java 多线程的运行框架以及我们是如何来控制线程的并发同步... -
Java多线程并发控制工具CountDownLatch,实现原理及案例
2020-01-11 12:11:56跟着作者的65节课彻底搞懂Java并发原理专栏,一步步彻底搞懂Java并发原理。 作者简介:笔名seaboat,擅长工程算法、人工智能算法、自然语言处理、架构、分布式、高并发、大数据和搜索引擎等方面的技术,大多数编程... -
如何解决多线程并发问题
2019-04-26 13:45:08多线程编程中的三个核心概念 原子性 这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。 关于原子性,一个非常经典的例子就是... -
RxJava和多线程并发
2019-05-28 20:18:23RxJava的核心是异步,但是异步绝对不等于并发,更不等于线程安全,因此使用的时候要避免。 Observables must issue notifications to observers serially (not in parallel). They may issue these notifications... -
Rust的并发编程(二) 多线程并发
2020-09-10 11:33:12文章目录Rust的并发编程(二)多线程并发创建子线程线程间通信使用通道传递数据共享内存 Rust的并发编程(二) 并发,是指在宏观意义上同一时间处理多个任务。并发的方式一般包含为三种:多进程、多线程以及最近几年... -
多线程并发修改异常(ConcurrentModificationE)产生的原因以及解决方案
2021-09-29 10:31:08并发修改异常一 什么是并发修改异常(ConcurrentModificationException)二并发修改异常(ConcurrentModificationException)产生的原因1 modCount是什么意思?2 expectedModCount是什么意思?3 modCount != ... -
多线程并发执行及解决方法
2019-02-02 13:10:49用一个案例来说明:假如我们要实现一个售票的小功能,用3个线程售出共2000张票。 初始模型为: package com.test7; public class synchronizedTest { public static void main(String [] args){ TicketWindow ... -
解决多线程并发问题
2017-12-21 03:02:26如果对该表的更新或插入的操作,都会经过一个统一的文件,这种方式是可以解决的多进程并发的问题; 实现方式如下: public static function cbInventoryReserve() { $LOCK_FILE_PATH = $_SERVER['DOCUMENT_...