我们一起来聊聊并发吧,one。
引言
最近工作当中写了一个有关并发的程序,引起了LZ对并发的强烈兴趣。这一下一发不可收拾,LZ用了一个多星期,看完了这本共280+页的并发编程书。之所以能看这么快,其实这主要归功于,自己之前对并发就有一定的理解。在这种前提下看书,其实只是一个印证自己之前想法的过程而已,因此看起来会比较快,而且在看的时候,会有多次这种感觉,“擦,原来还真是这样的”。
尽管LZ已经说了看书看的快的原因,但不管怎么说,书看的太快,肯定难免有遗漏。因此博客此时就派上用场了,它绝对可以帮你查缺补漏。因为在写的过程中,你会发现,之前你读的时候自以为理解的透透的东西,却无法给别人讲清楚。这就说明,你需要补补漏了。
并发的来源
并发的由来,从现在来看,似乎是必然的。因为人是一种懒惰而又急躁的动物,这是人的本性。因为急躁,并发就出现了,因为懒惰,多核时代就来了。
为什么这么说呢?
如果所有的程序都是串行的(之所以说如果,是因为LZ接触到电脑时,它已经是并发的了,因此只能想象一下),那么当你打开了一个特别慢的网页,最后你等不及想关掉浏览器的时候,你会发现,你必须等加载这个网页的事做完你才能关闭它。这是何其蛋疼的一件事。急躁的人们能允许这种事发生?因此并发就出现了。
再来想象一下,如果我们想让一个程序运行的更快,从直觉上讲,我们应该让CPU运算的更快。比如以前一秒可以计算1000次,现在我们让它在一秒内可以计算10000次。但是懒惰的人们发现,这种直觉上的方式似乎非常困难,想要硬生生的提高CPU的速度(缩短时钟周期)是非常困难的。因此懒惰的人们就想到一种偷懒的办法,一个CPU一秒可以计算1000次,两个的话不就可以计算2000次了(实际并非如此,但我们可以这么理解多个CPU带来的效率增加),这个相对简单的办法最终被人们采用了。因此多核时代就到来了。
并发的危险
说起并发引起的危险,在LZ的理解来看,主要来源于程序所给人带来的直觉造成的误导。这一点在LZ所写的计算机系统原理中有讲到(可以将这两本书的内容联系起来),比如下面这个程序,它给人的直觉是,a首先变成了1,然后b变成了2。
- int a = 1;
- int b = 2;
直觉是这样的,但往往是错误的,因为在程序真正执行的时候,可能是b先变成了2,a又变成了1,更奇葩的是,很可能a和b都始终是0。估计说起两者的赋值顺序颠倒,各位还可以理解,但是说到两者可能都是0,有的猿友就懵了,有种瞬间被颠覆三观的感觉,但是学习并发往往就是颠覆你三观的过程。
这种直觉与现实之间的不同,就给并发的程序造成了危险。它可能引起你预料之外的错误,而且往往是防不胜防。因此并发是诱人的,但也同样是危险的,一个诱人的东西永远都伴随着危险,就像高贵的玫瑰往往都是有刺的。
知道了上面这些,我们就可以来看看安全性和活跃性了。安全性是指,“程序不会出现糟糕的事情”,活跃性则是指,“好的事情一定会发生”。可以看出来,安全性更多的是在强调执行的程序是正确的,而活跃性更多的是在强调程序可以正确的往下进行(有点绕?那就对了)。
举个例子,对于一个并发递归求解的程序来讲,安全性则可以保证结果的正确性,而活跃性则可以保证这个程序总能得到一个正确的结果或者最终发现它没有解而抛出无解的异常。
Java的并发
对于大部分从事Java开发不久的程序猿来讲(包括LZ),并发一般都是很少接触到的(这里主要以LZ的领域来说,即J2EE),因为现有的框架已经将很多并发的问题给解决了,并给我们这些无脑程序猿创造了一个串行程序的环境。
比如,在J2EE领域的servlet规范当中,servlet是单例的,并且非常有可能,甚至可以说一定会被多个线程并发的去访问。因此servlet其实是有并发的安全性问题的,除非你不在servlet当中记录任何状态。但是当前比较火的MVC框架struts2已经帮我们解决了这个问题,尽管Action当中经常会有一些数据或者说状态,但Action在struts2中是非单例的,这相当于每个Action实例都是线程私有的,因此不存在并发问题。
有一些情况下,我们可能会接触到并发问题,比如,你需要做一个单例的对象,那么这个对象一般可以被全局访问,因此就可能存在并发的问题。可以这么说,几乎所有采用了单例模式的对象都会涉及到并发的问题,除非这个对象没有任何状态,但是这往往不会出现,因为没有状态的单例对象是没有意义的,它们更好的处理方式应该是一个无实例(即将构造函数私有化)且充满了静态方法的类。
很多程序猿在初次意识到并发时,都会采取一个看似万能却并非一定有用的方法,那就是将一个类的所有方法加上synchronized关键字。LZ以前也是这样的,而且还自认为十分高端,现在想想,LZ实在自惭形秽。当时LZ对synchronized的理解,就知道它可以让很多线程一个一个来执行这个方法,至于其它的特性,就不太明白了。
其实在大部分时候,一些比较简单的场景中,上面这种无脑做法还是能起到相应的作用的,也就是说,它可以保证安全性与活跃性。但是另外一个特性就无法保证了,那就是性能。无脑的方法同步,有时候会将性能降低数个数量级,可能会使很多线程都在等待,CPU却一直处于1%利用率的情况。
通常情况下,对于安全性、活跃性以及性能来说,我们会将性能放在最后一位,引用之前看过的一句经典的话,是用来形容面向对象设计的,即“可复用的前提是可用”。同样的,对于并发的程序来讲,“性能高的前提是程序的执行是正确的”。
小结
今天就暂且写这么多吧,对于并发,其实想说的还有很多,毕竟刚看完这本书。后面还会陆续给出自己的理解,但是会穿插着《计算机系统原理系列》的内容,这个系列也该继续前进了,因为并发已经耽误了它太久。
我们一起来聊聊并发吧,one。的更多相关文章
- 聊聊并发(七)——Java中的阻塞队列
3. 阻塞队列的实现原理 聊聊并发(七)--Java中的阻塞队列 作者 方腾飞 发布于 2013年12月18日 | ArchSummit全球架构师峰会(北京站)2016年12月02-03日举办,了解更 ...
- 聊聊并发-Java中的Copy-On-Write容器
详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp78 聊聊并发-Java中的Copy-On-Write容器 Cop ...
- 聊聊并发(一)深入分析Volatile的实现原理
本文属于作者原创,原文发表于InfoQ:http://www.infoq.com/cn/articles/ftf-java-volatile 引言 在多线程并发编程中synchronized和Vola ...
- 转:聊聊并发(八)——Fork/Join框架介绍
1. 什么是Fork/Join框架 Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架. 我们再通过 ...
- 聊聊并发(六)——ConcurrentLinkedQueue的实现原理分析
1. 引言 在并发编程中我们有时候需要使用线程安全的队列.如果我们要实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,另一种是使用非阻塞算法.使用阻塞算法的队列可以用一个锁(入队和出队用同一把 ...
- 聊聊并发(三)Java线程池的分析和使用
1. 引言 合理利用线程池能够带来三个好处.第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗.第二:提高响应速度.当任务到达时,任务可以不需要的等到线程创建就能立即执行. ...
- 聊聊并发——深入分析ConcurrentHashMap
术语定义 术语 英文 解释 哈希算法 hash algorithm 是一种将任意内容的输入转换成相同长度输出的加密方式,其输出被称为哈希值. 哈希表 hash table 根据设定的哈希函数H(key ...
- Java并发编程原理与实战一:聊聊并发
一.大纲 •你真的了解并发吗 •多线程和并发 •多线程和多进程 •线程一定快吗 •学习并发的四个阶段 •学习目标 •适合人群 •荐书 二.学习并发的四个阶段 •熟练掌握API,能够完成并发编程 • ...
- 聊聊并发(四)——深入分析ConcurrentHashMap
线程不安全的HashMap 因为多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap,如以下代码 final HashM ...
随机推荐
- pathlib
导入Path类 from pathlib import Path 创建Path对象 p = Path('C:\Windows\System32') # 用C:\Windows\System32创建Pa ...
- ORACLE数据库入门再在屋里坐会
一.数据库简介 数据库概述 数据库(database)是按照数据结构来组织,存储和管理数据的仓库,它产生与距今五十年前. 简单来说是本身可视为电子化的文件柜--存储电子文件的处所,用户可以对文件中的数 ...
- 对于socket发送数据时是否要加锁及write read的阻塞非阻塞
偶尔讨论到了socket发送数据时是否应该加锁的问题,就在网上查了一下,下面是大神陈硕的答案 对于 UDP,多线程读写同一个 socket 不用加锁,不过更好的做法是每个线程有自己的 socket,避 ...
- k8s存储 pv pvc ,storageclass
1. pv pvc 现在测试 glusterfs nfs 可读可写, 多个pod绑定到同一个pvc上,可读可写. 2. storageclass 分成两种 (1) 建立pvc, 相当于多个 ...
- Spring源码分析(二十三)BeanFactory的后处理
摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 目录 一.激活注册的 BeanFactoryPostProcessor ...
- Spring源码分析(十七)循环依赖
本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 实例化bean是一个非常复杂的过程,而其中比较难以理解的就是对循环依赖的解决, ...
- XCode iOS之应用程序标题本地化
1.XCode项目中创建一个.strings 扩展名的文件:打开File > New > File,选择Resource中Strings Fils,如图:点击下一步,为文件命名为(强烈建议 ...
- UITapGestureRecognizer 的用法(轻触手势识别器)
最近在项目中用到了手势操作,键盘回收时还是挺常用的,现在总结下,多谢网络上大神们的分享. 先分享下我在项目中用的代码: UITapGestureRecognizer * mytap=[[UITapGe ...
- Linux的常用基础命令
初入运维界,从Linux开始学起.首先掌握的是Linux的部分常用命令,总结如下: 1. ls ls = list 查看当前目录下所有文件信息,常用参数组合:ls -al (l 指详细信息 a指所有文 ...
- Redis数据类型基本操作
String类型: 设置键值对: set key value 设置键值对和过期时间:setex key seconds value ( 以秒为单位 ) 设置多个键值对: mset key1 value ...