并发编程是计算机学科重要的命题。 如何提纲挈领的掌握并发编程,搭建知识体系尤为重要。 这篇文章基于本身对于并发编程的理解和公开资料的整理,试图拨开迷雾,从总体上介绍并发编程。java
主要内容包括:编程
J.U.C框架缓存
并发编程是伴随计算机发展产生的。安全
第一代计算机使用笨重的卡片机, 工做人员将计算任务打孔的方式输入到计算机中。
为了提高运算速度,出现了批处理的操做系统。然后出现了分时操做操做系统。网络
英特尔(Intel)创始人之一戈登·摩尔提出:集成电路上可容纳的晶体管数目,约每隔兩年便会增长一倍。这就是著名的摩尔定律。半导体行业大体按照摩尔定律发展了半个多世纪,对二十世纪后半叶的世界经济增加作出了贡献。多线程
CPU从原来的单核逐渐增长成双核、四核甚至更多。 CPU变的愈来愈快, 内存和IO的速度的增加却很缓慢。缓存是为了缓解CPU和内存速度之间的速度不匹配, 内存缓解了CPU和IO设备之间的速度不匹配。架构
如今计算机存储结构以下图所示:并发
多线程和多进程的产生就是为了充分利用多核CPU的计算优点, 减小IO阻塞而产生的。框架
多线程程序有不少优势:异步
以前的操做系统只能新建少许的线程,所以,操做系统提供了高效的IO方式。好比多路复用IO,异步IO等。现代的操做系统,线程数量限制一般很大, 可使用线程池技术提高处理能力。
传统的GUI系统不少都是单线程, 经过主事件循环(Main Event Loop)处理界面组件各类事件。在处理比较耗时的事件操做时, 很容易卡住主界面。
如今的GUI系统利用多线程的技术, 使用事件分发机制(Event Dispatch Thread)代替主事件循环。 事件发生时调用对应的事件处理器。 界面系统影响更加灵敏。
一个线程处理一个事情比一次处理多个事情建模更加容易。 经过多线程的框架能够把事件处理和资源调度、交替执行的操做、异步IO和资源等待隔离开来。如今的并发编程的框架和组件(Servlet,RMI等)使得建模更加简单。
多线程是一把双刃剑、带来性能提高的同时带来安全性、活性和性能问题。
以酒店清洁为例子:
昆泰酒店有不少的房间须要打扫、保洁在主管的监督完成全部酒店房间的清洁工做。
公司资金有限, 只有有限的清洁工具供保洁轮流使用。
这个例子中:清扫房间就是接下来计算机要处理的计算任务。保洁至关于处理任务的线程,主管至关于线程调度器,有限的清洁工具至关于CPU资源或者其余公共资源。
那问题来了,酒店规模很小的时候,整个昆泰酒店只有1名保洁,这名保洁只须要逐个去打扫。处理速度虽然慢,可是不会出错。清洁工具能够被这名保洁独占,只有一名保洁,所以也不须要额外招聘保洁主管。
随着酒店规模变大,1名保洁不能知足酒店的要求。公司雇佣更多的保洁,并为这些保洁人员配置了保洁主管。
多名保洁打扫完房间以后会通知保洁主管该房间已经被打扫。清洁工具在使用完成,交给下一位保洁以前须要清扫干净并标记上本身的工号表示本身在使用(上下文切换)。
保洁之间协同工做和多线程系统很像。
保洁打扫一个房间,若是不作任何标记或者通知,别的保洁也可能进入打扫。形成了资源浪费(任务重叠执行)。更不幸的事情,新入住的房客在这个时候进入了房间看到房间一片狼藉,该做何想。
这种状况至关于公共资源访问时的线程安全性问题。 线程安全性问题有时候只是形成资源浪费,跟多的时候会形成严重的错误。
保洁主管发现了这个问题以后,他让保洁在进入房间门打扫以前在门口放置一个打扫中的牌子(锁)。另一个保洁看到这个牌子(锁失效),就知道房间在打扫中。这个问题也就解决了。
房间的打扫状态至关于Java语言中的锁。 锁不紧是能解决互斥访问,也间接实现了线程之间的通讯。
保洁主管为了有限的工具(资源)被高效利用设计了一套资源分配的规则(JMM Java内存模型)。 规则规定了不一样工具放置的位置(内存分布)、工具使用规则(保证工具使用状态每一个人均可见)、工序优化的规则(重排序)、工做分配的规则(原子性)。
主管指定的资源分配的规则,至关于Java的内存模型。
Java内存模型包括Java内存区域划分和内存使用规则(可见性、原子性和指令重排序)。
因为线程资源稀缺性或者程序自身的问题和缺陷致使线程线程一直处于非runnable状态,或者线程虽然处于runnable状态可是其要执行的任务却一直没法进展的现象被称为线程活性故障。
常见的线程活性故障包括死锁、活锁、饥饿。
死锁:
保洁A手里拿着拖把, 她须要抹布, 另一个保洁手里拿着抹布须要拖把。若是两我的都互不相互谦让的会就会形成死锁问题。
形成死锁须要四个条件: 互斥条件, 请求和保持条件, 不可剥夺和循环等待。 破坏四个条件任何一个,就能够解决死锁的问题。
活锁:并未产生线程阻塞,可是因为某种问题的存在,致使没法继续执行的状况。
这种时候通常是将不可修复的错误不要重试,或者是重试次数限定
好比两个颇有礼貌的人在同一条路上相遇,彼此给对方让路,可是又在同一条路上遇到了。互相之间反复的避让下去
这种时候能够选择一个随机退让,使得具有必定的随机性
优先级低的线程因为不停的被高优先级线程抢占执行,致使最终没法执行。
引入公平机制能够解决饥饿问题。 好比使用ReentrantLock实现的公平锁。
并发编程存在因为互斥资源争用致使程序吞吐降低,执行变慢等性能问题。
加锁能够解决线程资源争用的问题,但同时带来开销。
在 JDK1.6 以后,出现了各类锁优化技术,如轻量级锁、偏向锁、适应性自旋、锁粗化、锁消除等
synchronized关键字修饰的锁对象会根据互斥资源争用状况选择不一样的锁的实现。
当访问共享数据时,一般是要使用同步。若是要避免使用同步,就是不提供共享数据。若是仅在单线程中访问数据,就不须要同步,这种技术就叫作线程封闭。
实现线程封闭主要有三种方式:
同时JDK也提供了AtomicXXX使用CAS方式实现线程同步。
J.U.C是指java.util.concurrent包下的并发类。
J.U.C包里的类有部分:
线程安全相关的类包括以下三类:
类图以下所示:
为了提高房间清扫和人员使用的效率,主管提议使用动态人员的方式配置保洁(线程池)。 酒店天天至少有2名保洁值班(coreSize), 客人离店以后派遣2名保洁打扫。为了提高服务体验,若是有超过2个以上房客离店,主管将会从其余分部调派保洁。不幸的是总部规定每一个酒店最多只能有10个保洁(maxSize)。若是没有可用的房间,新来要入店的房客只能等待(Queue)。酒店资源有限,等待的队列不能无限的长(有界队列),不然有可能把酒店挤爆,哈哈哈,这种状况酒店就赚发了。在等待队列也满的时候, 须要合理策略(拒绝策略)处理。酒店前台可选的策略能够有:
拒绝接待新客人(DiscardPolicy), 不接待新客人通知总部(AbortPolicy,引起RejectExecutionException异常), 让客人本身打扫房间(CallerRunsPolicy), 队尾位置让给新来的客人(DiscardOldestPolicy)。
这就是线程池的模型。线程池模型核心关注:3个容量大小(coreSize, maxSize, queueSize)和4种拒绝策略(DiscardPolicy,AbortPolicy,CallerRunsPolicy,DiscardOldPolicy)。总部会根据酒店客流量状况安排资源池配置参数(设置coreSize,maxSize和QueueSize),这便是如何设置线程池参数的问题。
在默认ThreadPoolExecutor.AbortPolicy ,处理程序会引起运行RejectedExecutionException后排斥反应。
在ThreadPoolExecutor.CallerRunsPolicy中,调用execute自己的线程运行任务。 这提供了一个简单的反馈控制机制,将下降新任务提交的速度。
在ThreadPoolExecutor.DiscardPolicy中 ,简单地删除没法执行的任务。
在ThreadPoolExecutor.DiscardOldestPolicy中
须要打扫的房间很少的时候, ExcutorSevice的模式运行效率很好。中午12点以后,大量空闲房间须要打扫。 若是还按照线程池的模式进行任务分配, 保洁人员刚打扫完4层的房间,保洁主管打电话过来讲下一步打扫1楼的房间。保洁主管发现这种模式在须要打扫房间比较多的时间(任务比较多)时效率并不高。
为了解决这个难题, 保洁主管想到了一个办法。给每一个保洁分配一层楼(一个线程一个工做队列),有楼层退房比较多, 有的比较少,为了解决公平和效率的问题。保洁完成本层任务后,随机去其余楼层帮忙(work stealing)。
保洁主管想到的这个方法就是Fork/Join模型。 Fork/Join相似ExecutorService
又有不少不一样的地方。 Fork/Join能够把大任务(酒店),拆分红小任务(房间/楼层)。
Fork/Join模型和ExecutorService模型最主要两点区别是
(1) 一个线程一个工做队列:每一个人负责一个楼层。
(2) 工做窃取:忙完本身的楼层,随机去其余楼层帮忙。
多线程编程是实现并发编程主要途径。 经过线程可以大大提高系统吞吐和性能。可是,对于多线程编程要解决两个核心问题:
咱们可使用等待/通知,共享变量,管道等方式进行线程通讯,好比Object的wait/notify, Condition的await/notify, LockSupport的park/unpark, volatile,InheritableThreadLocal, PipeInputStream/PipeOutputStream等。
线程同步咱们能够加锁的技术(synchronized,Lock),也可使用无锁技术(CAS)。
因为篇幅的缘由。关于线程通讯和线程同步的问题,会单独写一篇文章去讲解。
回复“资料”,免费获取 一份独家呕心整理的技术资料!