并发思想提炼(2)(Lock free,轮询及线程池)

8.    告别Lock

不是一直说Lock比较麻烦危险吗,那就不要好了。其实有一个Lock free的方法。

首先引入一个概念——原子变量。在这种变量上的操作是原子操作(atomic operation)。原子操作就是说这个操作要么都完成,要么都不完成,部分完成是不行的。就像物理化学中的原子一样,借用不可再分的意思。按照这样理解,对这个原子变量的访问操作就必定是串行的。一个原子操作完成后才能进行另一个原子操作。这样子的变量类型多半是基本变量,什么int啊double啊boolean啊之类。

就可以简单这样想,只要调用原子操作,就能保证对象的串行访问。

常用语言原子操作内部实现基本用到了锁。原子操作常用的有++,--,compare & swap。特别地,这个CAS(compare and swap)配合循环操作就能实现lock free方法。看下面的stack.push(…)伪代码。

1 Atomic<Node*> head;
2 Node* new_node= new Node(….);
3 New_node->next=head; (1)
4 While(!head.compare_and_swap(new_node->next, new_node)); (2)

设有一个Stack,需要push一个值进去,head指针申明为原子量。(1)把新值的next设为head。(2)更新head,如果head==new_node->next那么,head=new_node,返回true,跳出循环;否则new_node->next更新为head,并返回false,继续循环。猜想?什么情况会new_node->next不等于head?如果在(1)执行完毕后head被其它线程修改。此时的old_head(new_node->next)就需要更新为当前最新的head,才能进行接下来的操作。这种方式实现的stack push就是用了lock free思想。是不是比加lock代码看起来简洁多了? 代码中一个lock操作都没有看到哦。

Lock free需要一个自旋锁,耗CPU时间,而lock方法是直接block住,释放占用的CPU时间。发现没?这个“自旋锁”思想在前文的try lock中也出现了?甚至可以这样猜想,如果把所有的锁都换成自旋锁,是不是就能防止死锁了?前面的stack.push操作也可以添加CAS试做次数,操作一定次数或时间限制,表示此次push失败,也就跳出了此自旋锁。另外,没有所谓的lock free就是比lock好,根据不同的业务情况,自行选择吧。

问:如果我的关键实体是一个对象,那么可以把对象最为一个原子量吗?这个,恐怕不能,不过可以分解对象中的一些基本类型字段作为原子量。到底使用哪些基本类型,需要对业务需求有深刻的理解。

9.    轮询操作

观察者设计模式这里不多说了,主要思想是把主动轮询,转变为被动通知。不过在某些并发程序集中“轮询”比“通知”思想还要普遍,或者说“通过轮询来通知”。甚至这样说,我感觉在某些场合轮询比通知更有用,特别是对顺序要求敏感的地方。这类程序基于规避乱序风险从而选择此架构方式,而且轮询的架构方式能人为制造“程序栅栏”。Thread barrier这个操作听说过吧,这是一种同步程序的方式。这种方式在某些分布式程序中用到(特别是消息队列模块),而且非常利于Debug。

一个完整的系统轮询和通知都很重要,没有谁好谁不好一说。写到这里想想,在上文轮询中,提到“顺序”两个字,你懂的,顺序执行容易引发性能瓶颈,及其连锁反应。而且轮询数据库什么的,数据多起来能直接把系统搞得不响应好吗,考虑下触发器嘛。所以架构就是一种舍一种得的心态,要根据具体业务需求来定夺。

“通知”的理解以后有机会再说。对了,轮询时记得yield, 别到时候轮询线程一跑起来把CPU时间片占光了,其它并发操作受到影响。

10.    线程操作

线程是执行一个函数,很有函数式编程理念的感觉。但是并不是线程开得越多程序处理就越快,这和实际处理器数和线程上下文切换频率就很大关系,这些情况baidu google一下就知道,不细说,基本上最好的线程数量就大概是空闲处理器的数量。

这里说的一个思想是线程池Thread pool。就是说线程创建后不会因为执行完毕而被销毁,它会放入一个池子中等待新任务唤醒它,然后开始执行。线程创建是要耗CPU资源的,重复利用当然是好的。这个功能在高级语言中有对应的实现,比如说C#的task,它的默认调度类就实现了此思想。所以说高级语言使用起来比较顺畅,你不用从头开始造轮子。C++的boost库中也有类似方法。编程的时候用上呗,何乐而不为?

如上图,2个线程做5个任务,而不是用5个线程。而且在task3之后,线程1就处于空转或阻塞的等待状态而不是销毁自身,直到task4出现又开始执行。但是,如果5个task并行执行,且确实有如此多空闲处理器,开5条线程,处理效率更高。线程池的方式是否适用此场景还需好好考虑下。

我们看得稍微抽象点,前文所说的线程池技术,就是任务调度操作,这个以后有机会在说吧。。。

时间: 2024-10-05 19:49:01

并发思想提炼(2)(Lock free,轮询及线程池)的相关文章

高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?

(1)高并发.任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换(2)并发不高.任务执行时间长的业务要区分开看:a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换(3)并发高.业务执行时间长,解决这种类型任务

Java多线程与并发应用-(9)-锁lock+条件阻塞conditon实现线程同步通信

一. lock可以代替synchronized关键字实现互斥功能.使用方法如下: Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); } 需要注意的是. 1.需要互斥的一个或多个方法要使用同一个互斥锁. 2.在被锁包含的代码块中,要使用finally块将锁释放. 二. Condition的await方法(注意不是wait方法)可以替换传统通信中的wa

java并发的艺术-读书笔记-第九章线程池

使用线程池的好处: 1.降低资源消耗:减少了线程创建和销毁的资源消耗 2.提高响应速度,当任务到达时,线程可以不尽兴创建直接处理 3.提高线程的可管理性.使用线程池可以对线程进行统一的管理,监控,使用. 线程池的源码分析: public void execute(Runnable command){ if(command==null){ throw new NullPointerException(); } //如果执行线程数小于基本线程,则创建线程,并执行任务 if(poolsize>=cor

Java并发基础(六) - 线程池

Java并发基础(六) - 线程池 1. 概述 这里讲一下Java并发编程的线程池的原理及其实现 2. 线程池的基本用法 2.1 线程池的处理流程图 该图来自<Java并发编程的艺术>: 从图中我们可以看出当一个新任务到线程池时,线程池的处理流程如下: 线程池首先判断线程池里面线程数是否达到核心线程数.如果不是则直接创建新线程作为核心线程来执行该任务(该线程作为核心线程不会由于任务的完成而销毁),否则进入下一流程. 判断阻塞队列是否已经满了.如果没满则将该任务放入阻塞队列中,等待核心线程处理,

Java并发编程从入门到精通 - 第6章:线程池

1.什么是线程池(为什么使用线程池):2.Executor框架介绍:  Java 5中引入的,其内部使用了线程池机制,在java.util.cocurrent 包下,通过该框架来控制线程的启动.执行和关闭(使用该框架来创建线程池),可以简化并发编程的操作:  Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等:  ExecutorService接口继承自Executor接口,

JAVA多线程提高六:java5线程并发库的应用_线程池

前面我们对并发有了一定的认识,并且知道如何创建线程,创建线程主要依靠的是Thread 的类来完成的,那么有什么缺陷呢?如何解决? 一.对比new Threadnew Thread的弊端 a. 每次new Thread新建对象性能差. b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom. c. 缺乏更多功能,如定时执行.定期执行.线程中断.相比new Thread,Java提供的四种线程池的好处在于:a. 重用存在的线程,减少对象创建.消亡的开销,性能

Java并发(四)线程池监控

目录 一.线程池监控参数 二.线程池监控类 三.注意事项 在上一篇博文中,我们介绍了线程池的基本原理和使用方法.了解了基本概念之后,我们可以使用 Executors 类创建线程池来执行大量的任务,使用线程池的并发特性提高系统的吞吐量.但是,线程池使用不当也会使服务器资源枯竭,导致异常情况的发生,比如固定线程池的阻塞队列任务数量过多.缓存线程池创建的线程过多导致内存溢出.系统假死等问题.因此,我们需要一种简单的监控方案来监控线程池的使用情况,比如完成任务数量.未完成任务数量.线程大小等信息. 一.

【Java并发工具类】Lock和Condition

前言 Java SDK并发包通过Lock和Condition两个接口来实现管程,其中Lock用于解决互斥问题,Condition用于解决同步问题.我们需要知道,Java语言本身使用synchronized实现了管程的,那么为什么还在SDK中提供另外一种实现呢?欲知为何请看下文. 下面将先阐述再造管程的理由,然后详细介绍Lock和Condition,最后再看实现同步机制时是选择synchronized还是SDK中的管程. 再造管程的理由 Java本就从语言层面实现了管程,然而后面又在SDK中再次现

JAVA多线程编中的轮询锁与定时锁

显示锁                                                                                     Lock接口是Java 5.0新增的接口,该接口的定义如下: 1 2 3 4 5 6 7 8 publicinterface Lock {     void lock();     void lockInterruptibly() throws InterruptedException;     boolean tryLo