当前位置: 首页 > 科技观察

并发编程的三个核心问题

时间:2023-03-17 20:28:55 科技观察

并发编程不是孤立的技术,也不是脱离现实生活场景提出的技术。相反,并发编程是一门综合性的技术,同时又与现实生活场景有着密切的联系。并发编程中存在三个核心问题:分工、同步、互斥。本文简要介绍这三个核心问题。1分工对于分工比较官方的解释是:将一个比较大的任务拆分成多个大小合适的任务,将这些大小合适的任务交给合适的线程去执行。分工强调执行力的表现。▊类比真实案例你可以通过类比真实场景来理解分工。比如你是一家上市公司的CEO,那么你的主要工作就是规划公司的战略方向,把公司管理好。至于如何把公司经营好,还有更多的工作要做。在这里,管理好公司可以看作是一项大任务,可以包括人员招聘和管理、产品设计、产品开发、产品运营、产品推广、税收统计和计算等。如果把这些任务都交给CEO一个人的话,估计CEO会累死的。CEO一个人完成公司所有的日常工作,如图1图1CEO一个人完成公司所有的日常工作如图1所示,公司的CEO包揽所有事情是非常不可取的单单是公司的日常工作,就会导致公司无法正常运作,那么应该怎么办呢?一个好的办法是将公司的日常工作进行分解,将人员招聘和管理分配给人力资源部,产品设计分配给设计部,产品开发分配给研发部,产品运营和产品推广。分别交给业务部和市场部,将公司的税务统计和核算交给财务部。这样一来,CEO的重点工作就变成了及时了解各部门的工作,统筹协调各部门的工作,思考如何规划公司的战略。分工后的公司日常工作如图2所示图2分工后的公司日常工作将公司的日常工作进行分工后,可以发现各部门的工作可以并行推进。例如,人力资源部在进行员工绩效考核时,设计部和研发部正在设计和开发公司的产品。同时,公司运营人员正在与设计人员、研发人员就如何更好地改进公司产品进行沟通,而市场部正在加紧宣传和推广公司的产品,财务部正在统计和计算公司的各种财务报表。一切都是那么有条不紊。所以,在现实生活中,安排合适的人做合适的事是非常重要的。映射到并发编程领域也是如此。▊并发编程中的分工在并发编程中,还需要将一个大任务拆分成若干个较小的任务,并将这些小任务分配给不同的线程执行,如图3所示。图3将一个大任务拆分成若干个smallertasks在并发编程中,由于可以并发执行多个线程,因此可以在一定程度上提高任务的执行效率。在并发编程领域,另外一个需要注意的问题是:将任务分配给合适的线程去做。也就是说,本该由主线程完成的任务不应该交给子线程处理,否则无法解决问题。这就好比一家公司的CEO,把规划公司未来的工作交给了产品开发人员。不仅不能规划公司的未来,甚至可能与公司的价值观背道而驰。在Java中,线程池、Fork/Join框架、Future接口都是实现分工的方式。在多线程设计模式中,GuardedSuspension模式、Thread-Per-Message模式、Producer-Consumer模式、Two-phasetermination模式、Worker-Thread模式和Balking模式都是实现问题分工的方式。2同步问题在并发编程中,同步是指一个线程执行完自己的任务后,如何通知其他线程继续执行任务。也可以理解为线程之间的协作。同步强调执行性能。▊类比真实案例你可以在现实生活中找到类似于并发编程中同步问题的案例。比如张三、李四、王五三人共同开发一个项目。张三是一名前端开发人员。他需要等待李斯的开发接口任务完成后,才能开始渲染页面,而李斯则需要等待王五的服务开发。工作完成后编写接口。也就是说,任务之间存在依赖关系,只有前一个任务完成后才能执行后续任务。在现实生活中,这个任务的同步更多的是通过人与人之间的沟通交流来实现的。比如王五的服务开发任务完成了,他告诉李斯,李斯马上开始实施界面开发任务。李四的接口开发完成后,告诉张三,张三立即调用李四开发的接口,将返回的数据渲染到页面上。现实生活中的同步模型如图4所示。图4现实生活中的同步模型从图4可以看出,在现实生活中,张三、李四和王五的任务是相互依赖的,而张三的任务页面的渲染取决于李四的开发接口。任务完成后,李斯开发界面的任务就交给王五开发服务的任务了。▊并发编程中的同步在并发编程领域,同步机制是指在一个线程的任务执行完成后通知其他线程继续执行任务的方式。并发编程的简单同步模型如图5所示。图5并发编程的简单同步模型从图5可以看出,在并发编程中,多个线程之间的任务是相互依赖的。线程A需要阻塞等待线程B执行完任务才能开始执行任务,线程B需要阻塞等待线程C执行完任务才能开始执行任务。线程C执行完任务后,会唤醒线程B继续执行任务,当线程B执行完任务后,会唤醒线程A继续执行任务。这种线程间的同步机制可以用下面的if伪代码来表示。if(dependanttaskcompleted){执行当前任务}else{继续等待依赖任务执行}上面if伪代码所代表的意思是:当依赖任务完成时,执行当前任务;否则,继续等待依赖任务的执行。在实际场景中,往往需要及时判断依赖任务是否已经完成。这时候可以用while循环代替if判断。while伪代码如下。while(dependenttaskisnotcompleted){continuetowaitfortheexecutionofthedependenttask}执行当前任务上面while伪代码的意思是:如果依赖任务没有完成,则等待依赖任务完成在执行当前任务之前。在并发编程领域,同步机制有一个非常经典的模型——生产者消费者模型。如果队列已满,生产者线程需要等待。如果队列未满,则需要唤醒生产者线程。如果队列为空,则消费者线程需要等待。如果队列不为空,则需要唤醒消费者线程。下面的伪代码可以用来表示生产者-消费者模型。Producerwhile(queueisfull){producerthreadwaits}唤醒producer伪代码while(queueisempty){consumerwaits}wakeupconsumerJava中的Semaphore,Lock,synchronized.,CountDownLatch工具类或框架如,CyclicBarrier,Exchanger和Phaser实现了同步机制。3互斥问题在并发编程中,互斥问题一般是指同一时刻只允许一个线程访问临界区的共享资源。互斥强调多个线程执行任务的正确性。▊类比真实案例现实中互斥问题的一个典型场景是多辆车在一个路口并入单行道,如图6所示。图6多辆车在一个路口并入单行道从图6可以看出,当多辆车通过一个路口并入同一条单行街道时,由于单行街道的入口只能容纳一辆车,其他车辆需要等待前面的车辆到达通过单向入口,然后有序通过单向入口。这就是现实生活中的互斥场景。▊并发编程中的互斥在并发编程中,分工和同步强调任务执行的性能,而互斥则强调任务执行的正确性,即线程安全。如果在并发编程中,多个线程同时进入临界区访问同一个共享变量,可能会出现线程安全问题,这是由线程的原子性、可见性和顺序性引起的。并发编程中解决原子性、可见性和顺序性问题的核心是线程间的互斥。比如可以使用JVM中提供的synchronized锁来实现多线程之间的互斥。使用同步锁的伪代码如下。修改方法publicsynchronizedvoidmethodName(){//省略具体方法}修改代码块publicvoidmethodName(){synchronized(this){//省略具体方法}}publicvoidmethodName(){synchronized(obj){//省略具体方法}}publicvoidmethodName(){synchronized(ClassName.class){//省略具体方法}}修改静态方法publicsynchronizedstaticvoidstaticMethodName(){//省略具体方法}除了synchronized锁,Java还提供了ThreadLocal、CAS、原子类、以CopyOnWrite开头的并发容器类、Lock锁、读/写锁等,它们都实现了线程的互斥机制。