Java 并发编程(一):摩拳擦掌
这篇文章的标题原本叫做——Java 并发编程(一):简介,作者名叫小二。但我在接到投稿时觉得这标题不够新颖,不够吸引读者的眼球,就在发文的时候强行修改了标题(也不咋滴)。
小二是一名 Java 程序员,就职于沉默公司,工龄是两年零一个月零三天。和刚毕业那会相比,编程能力已经大有提升,但领导老王一直没敢把并发编程的开发安排给小二,这让小二心里耿耿于怀。
这事不怪老王,小二心里很清楚:编写正确的程序很难,编写正确的并发程序更是难上加难。自己功力还不到那个份上,万一搞砸了,难免让一向谨慎的老王面上无光。
小二想来想去,办法只有一个,主动去学!就找老王要了一本《Java并发编程实战》,据说这本书是并发编程中的经典之作。拿到书后,随手翻了翻,竟然发现里面藏着一封情书:小二激动坏了,想象着老王写情话的样子,不由得笑出来声。
(戛然而止)
小二的背景就先介绍到这。接下来,我们来一起鉴赏下小二读完这本书后写下的第一篇文章。
Java 并发编程(一):简介
01、为什么需要操作系统
我喜欢在写文章(不用纸和笔用电脑了)的时候听音乐(不用 MP3 用电脑了),假如电脑只能做一件事情的话,我就只能在写完文章的时候再听音乐,或者听完音乐的时候再开始写作,这样就很不爽——在没有操作系统前,的确就是这么不爽。
有了操作系统后,情况就变得大不一样了,电脑可以同时运行多个程序。通过 TOP
命令可以查看电脑上当前正在运行的进程(和程序有着密切的关系),见下图。
通常情况下,一个程序会至少对应一个进程。上图中,“Google Chrome”这三个进程意味着我的电脑上打开着一个名叫谷歌浏览器的程序。
让我们用一段专业的术语来描述一下程序和进程之间的关系:
程序是计算机为完成特定任务所执行的指令序列。 操作系统允许多道程序并发执行共享系统资源,而程序在并发执行时所产生的一系列特点使得传统的程序概念已经不足以对其进行描述,因此,引入了“进程(Process)”:可以更好的描述计算机程序的执行过程,反映操作系统的并发执行、资源共享及用户随机访问的特性,并以此作为资源分配的基本单位。
每当一个程序运行时,操作系统就为该程序创建了一个进程,并为它分配资源、调度其运行。程序执行结束后,进程也就消亡了。一个程序被同时执行多次,系统就会创建多个进程。因此,一个程序可以被多个进程执行,一个进程也可以同时执行多个程序。
当然了,对于现在的操作系统来说,进程并不是最小的调度单位,而是线程。线程也被称为轻量级进程。
由于同一个进程中的所有线程会共享进程的内存地址空间,因此这些线程都能访问相同的变量,如果没有明确的同步机制来协同对共享数据的访问,那么当一个线程正在使用某个变量时,另外一个线程可能同时访问这个变量,就会造成不可预测的结果。
02、多线程的优势
查看了一下,我这台电脑的物理 CPU(处理器)个数只有一个,但是核数(一块 CPU 上面能处理数据的芯片组的数量)是 4 个。
这意味着,我这台电脑能够在同一时间处理一个进程内的四个线程任务:线程 A 正在读取一个文件,线程 B 正在写入一个文件,线程 C 正在计算一个数值,线程 D 正在进行网络传输。
我们知道,进行文件读写或者网络传输通常会发生阻塞,这也是没办法的事。如果没有多线程的帮助,程序会按照顺序依次执行,也就意味着发生阻塞的时候其他任务只能干巴巴的等着,什么也做不了。
有了多线程,情况就完全不一样了,线程之间可以互不干扰,从而发挥处理器的多核能力。
说个有点让人难为情的事,我是 Eclipse 的(愚)忠实用户,至今没切换到 IDEA 阵营。在用 Eclipse 的时候经常会出现这样的情况,一个进度被另外一个卡住,下一个必须等待上一个执行完毕才开始执行。等待的时候几乎什么也干不成,点了取消也没用!
假如 Eclipse 采用多线程的话,每个任务放在单独的任务中执行,响应就会快很多。
03、多线程带来的风险
曾有这样一则耳熟能详的故事。
特洛伊人在城外的海滩上发现了一只巨大的木马,他们把它拉进了城里而不是把它烧掉或推到海里,以为这是天神给特洛伊人带来的赐福。于是,特洛伊人欢天喜地,庆祝胜利,他们跳着唱着,喝光了一桶又一桶的酒,以为希腊人被他们战败了。
而故事的结局大家也都知道了。希腊人把特洛伊城掠夺成空,烧成一片灰烬。海伦(宙斯之女,被称为“世上最美的女人”,她和特洛伊王子私奔,引发了特洛伊战争)也被墨涅依斯带回了希腊。
多线程带来了无与伦比的好处,但也潜藏了巨大的风险(就像那个木马)。其中尤为突出的就是安全性问题。
public class Unsafe {
private int chenmo;
public int add() {
return chenmo++;
}
}
上面这段代码在单线程的环境中可以正确执行,但在多线程的环境中则不能。递增运算 chenmo++
可以拆分为三个操作:读取 chenmo,将 chenmo 加 1,将计算结果赋值给 chenmo。两个线程可能交替执行,发生下图中的情况,于是两个线程就会返回相同的结果。这也是最常见的一种安全性问题。
其次,多线程还会引发活跃性问题:线程 B 需要等待线程 A 释放它们共有的资源,而线程 A 由于一些问题导致无法释放资源,那么线程 B 就只能苦苦地等下去。
再者,多线程还会引发性能问题(设计良好的多线程当然会提高性能):当线程调度器临时挂起一个活跃中的线程转而运行另外一个线程时,就会频繁地出现上下文切换(Context Switch)——开销很大(挣得多花的也多)。
04、单核 CPU 和多核 CPU
来思考一个问题吧。假如 CPU 只有一个,核数也只有一个,多线程还会有优势吗?
闭上眼,让思维旋转跳跃会。
来看答案吧。
单核 CPU 上运行的多线程程序,同一时间只有一个线程在跑,系统帮忙进行线程切换;系统给每个线程分配时间片(大概 10ms)来执行,看起来像是在同时跑,但实际上是每个线程跑一点点就换到其它线程继续跑。所以效率不会有所提高,线程的切换反到增加了系统开销。
那多核 CPU 呢?
当然有优势了!多核需要多线程才能发挥优势(不然巧妇难为无米之炊啊),同样,多线程要在多核上才能有所发挥(好马配好鞍啊)。
多核 CPU 多线程不仅善于处理 IO 密集型的任务(减少阻塞时间),还善于处理计算密集型的任务,比如加密解密、数据压缩解压缩(视频、音频、普通数据等),让每个核心都物尽其用。
05、最后
亲爱的读者朋友们,小二投稿的第一篇文章到此就结束了。你对此感到满意吗?或者说你期待下一篇吗?
(此时的小二正在翘首以盼)
上一篇:Java:并发不易,先学会用
下一篇:如何保证共享变量的原子性?
Java 并发编程(一):摩拳擦掌的更多相关文章
- Java 并发编程(二):如何保证共享变量的原子性?
线程安全性是我们在进行 Java 并发编程的时候必须要先考虑清楚的一个问题.这个类在单线程环境下是没有问题的,那么我们就能确保它在多线程并发的情况下表现出正确的行为吗? 我这个人,在没有副业之前,一心 ...
- 【Java并发编程实战】----- AQS(四):CLH同步队列
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...
- 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport
在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...
- 【Java并发编程实战】----- AQS(二):获取锁、释放锁
上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...
- 【Java并发编程实战】-----“J.U.C”:CLH队列锁
在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...
- 【Java并发编程实战】-----“J.U.C”:CountDownlatch
上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...
- 【Java并发编程实战】-----“J.U.C”:CyclicBarrier
在上篇博客([Java并发编程实战]-----"J.U.C":Semaphore)中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier.在JDK API中是这么 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock
ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...
- Java并发编程:volatile关键字解析
Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...
随机推荐
- 【小程序】小程序中设置 tabBar
小程序中 tabBar 的设置,tabBar 就是底部导航栏,在app.json中配置. list 为数组至少两项.tab栏的 position 为 top 时间,不显示图标. "tabBa ...
- 通信模型socket
I/O的概念 操作系统的分为socket的I/O还有用户界面的输入输出,一般一个输入操作分为两个不同的阶段,1)等待数据准备好:2)从内核向进程复制数据 从理论上来讲,阻塞I/O.非阻塞I/O.复用I ...
- [Abp vNext 源码分析] - 6. DDD 的应用层支持 (应用服务)
一.简要介绍 ABP vNext 针对于应用服务层,为我们单独设计了一个模块进行实现,即 Volo.Abp.Ddd.Application 模块. PS:最近博主也是在恶补 DDD 相关的知识,这里推 ...
- configASSERT( uxCriticalNesting == ~0UL );问题
今天在单步调试FreeRTOS时,一直进入port.c 中的configASSERT( uxCriticalNesting == ~0UL ):函数.照片如下 上网一查,并且结合这个英文注释,才发现, ...
- PyCharm 配置 Git 教程
之前给大家介绍了 Git 安装及使用指南,今天再给大家介绍一下在 PyCharm 中使用 Git. 1 打开 File -> Settings -> Version Control -&g ...
- NDK jni mk文件 so文件 巴啦啦 初体验
概念JNI(Java Native Interface,Java本地接口),实现了Java和其他语言的交互(主要是C/C++),如:Java程序通过JNI调用C/C++编写的在Windows上运行的D ...
- python 实现爬取网站下所有URL
python3 实现爬取网站下所有URL 获取首页元素信息: 首页的URL链接获取: 遍历第一次返回的结果: 递归循环遍历: 全部代码如下: 小结: python3.6 requests && ...
- Java 设置PDF文档浏览偏好
在查看PDF文档时,可进行一些浏览偏好设置,例如是否全屏浏览.隐藏或显示菜单栏/工具栏.设置页面布局模式等,下面将通过Java编程的方式来演示如何设置. 使用工具: Free Spire.PDF fo ...
- 90后iOS开发者的出路,如何规划30岁前的自己(程序员必修课)
前言: 最近发生了一些和我们没有直接关系但是有间接关系的事情.比如华为“清洗”高龄基层员工,比如游戏公司2号员工拿不到股份而离职.先不说事实到底如何,起码很多码农是心有戚戚焉. 最近一年多也发生了一些 ...
- AndroidSDK的目录详解
Tools 目录工具(必须的工具) Android SDK Tools(必须,只需下载一个版本,一般选最新版本):基础工具包,版本号带rc字样的是预览版. Android SDK Platform-t ...