Java并发原理谈谈

进程、线程和并发实体

《操作系统原理》里面很重要的一个概念是进程。进程是程序动态的概念,它用来表示程序在执行的一组数据结构 。这组数据结构中记录了指令加载到内存中的地址,打开的文件,线程信息,共享内存等。

每个进程可以有多个线程。它也是一组数据结构包括:下一条要执行的指令,寄存器,堆栈,状态等。一幅图来表示

上图画出了4个线程(线程2、3、4和1是一样的,没有全画),如果程序没有启动任何线程,其实也会用到线程——主线程(图中的线程1)。 所以最后消耗CPU的线程而不是进程

其实在Linux中(我实在不知道Windows,我猜应该差不多)进程和线程是同一个数据结构——task_struct,对于内核(kernel)来说并没有进程和线程的区别,只有进程——kernel称之为task。所以 在Linux中进程和线程并没有父子关系而是平行的结构 ,表示进程的数据结构填充的数据多一些,包括了打开文件,共享内存之类的,这个被称为主线程;其他线程的数据结构这些项目则为空,并且有一个“父进程”的指针,指向了“主线程”。

明白了这一点我们就清楚了, 操作系统调度的最小对象其实是——线程 ,但是名字叫task,教科书上叫进程。。。。有点混乱了吗?所以我们引入一个新的术语—— 并发实体 。所有CPU调度的最小单位我们统称为并发实体,无论是进程还是线程或者是其他的什么“怪胎”。(没错,我会在下一次介绍这些怪胎。)

为什么要线程

勇敢提出这个问题的人要受到表扬,他冒着被无情嘲讽的危险提出了一个很白痴的问题。(我觉得回答不出来这个问题的人,才是真正的白痴)回答这个问题要回答另一个基本的问题——为什么要并发

我们想象一下,一个Web服务器,可能是下面的代码

while (true){
     request = next_http_request()
     request_work(request)
}

程序循环获取新请求(next_http_request),执行请求(request_work),然后继续下一次循环。request_work会从硬盘读取文件,然后发送给客户端作为HTTP的响应,而硬盘I/O是一个阻塞操作,也就是说request_work会一直等待读取完数据之后才能释放CPU的控制权,然后下一个请求才有机会被执行。

这就是并发要解决的问题,当request_work发起I/O之后CPU是完全空闲下来的,而可怜的新请求(next_http_request)必须等待I/O完成之后才可以获取CPU的控制权。所以CPU的利用率非常低, 并发要解决的问题就是提高CPU的利用率 。明白这一点我们也就清楚了,并发只对非CPU密集型程序管用,如果CPU利用率非常高,更多的并发只会让情况更加糟糕

那么并发为什么一定是多线程而不是多进程呢?其实在Linux下进程和线程的创建成本没有什么区别(都是task_struct),但是进程之间可以共享数据的方式只能通过非常复杂的IPC来实现, 线程之间代码都是共享的,地址空间也是共享的,所以共享数据的方式更加高效。 (进程要考虑隔离,一个进程没有办法直接访问另一个进程;线程不用隔离,线程之间共享内存)

我们修改成多线程版本的Web服务器

while (true){
     request = next_http_request()
     request_work_in_thread(request)
}

request_work_in_thread方法会启动一个线程(work线程),然后CPU开始执行next_http_request获得下一个请求。

request对象是在主线程创建的,可以直接传递给request_work_in_thread中的work线程使用。

我们提高CPU利用率所以需要并行,我们要提高并发实体之间共享数据的效率所以选择了线程作为并发实体的实现

Java的线程

好了,回到了Java。在Java中启动一个线程非常简单——只要new Thread就搞定了。JVM会把它变成操作系统的API,如果是Linux则会生成一个task_struct的结构。至于Runable之类的东西其实最后还是Thread,即便是Java并发包最后也还是用Thread。

所以至此,我们成功把《操作系统原理》中进程、线程和Java的线程“融汇贯通了”。下面开始另一个东西——PV操作。

竞争条件,临界区、PV信号量

恩,你这一部分估计也已经还给老师了。没关系,我们一起回忆一下。

举个例子:

public void plus(int value){
  count = count + value;
}

当多线程同时调用plus的时候程序的逻辑是错误的。count+value并不是一个原子操作,它会被变成三个CPU指令

  • 获取count的数据到寄存器(还记得吗?CPU只访问寄存器)
  • 寄存器+value,并且写回寄存器
  • 寄存器写回到内存

    如果T1,T2两个线程同时执行(count=0)

  • T1 获取count的数据到寄存器
  • T1 将寄存器的值加10
  • T2 获取count的数据到寄存器
  • T2 将寄存器的值加30
  • T2 寄存器写回到内存(count=30)
  • T1 寄存器写回到内存(count=10)

    我们期望的可能是40,但是实际情况是10,因为T2访问数据的时候T1还没有来得及写回到内存中。当两个线程访问同一个数据,最后的结果依赖于线程的顺序这个就叫竞争条件。避免竞争条件的方法就是——通过临界区把一组动作“原子化”。例子中就是把:count = count + value,原子化。(原子化是指一次做完;其他人排队等候。)

就像你去买咖啡,收银员是所有人共享的(竞争条件),如果他经历:问杯型、种类、口味;收费;给你发票,这三个过程不能被打断否则会乱掉的。所以大家需要依次排队。(临界区)

如何实现临界区?答案是PV信号量(也叫PV操作,PV原语)。它是著名“河南籍”计算机科学家——E.W.Dijkstra设计的一套算法,老爷子这套严密的理论是现代并发的基础。简单来说他定义了两个操作

设一个计数器s

  • P s-1,如果s小于0则休眠否则继续执行
  • V s+1,如果s<=0则唤醒等待进程否则继续执行

P操作相当于使用资源,执行这个操作相当于:这个桌子我承包了,你们等我用过之后再用。(如果桌子有人那就只能乖乖等着了)

V操作相当于释放资源,执行这个操作相当于:这个桌子我不用了,下一个是谁?来用吧。(如果没有下一个人,那就直接走人了)

没错,PV就是“锁”。《操作系统原理》中竞争条件、临界区都是现实中不存在的概念,只有PV操作被具体实现了,就是我们称之为锁的东西。

Java中的锁

所有的“锁”都是一种PV操作,锁的区别在于你选择的“计数器”是什么?比如你选择的计数器是当前对象那么对应的关键是“synchronized”(还有个名字叫管程,天真的科学家们觉得面向对象的锁好牛B,就赐予它一个专门的名词),如果你的计数器是一个原子类型的值那么可能就是AtomicInteger的inc或者dec操作。这个就是 锁的粒度 ,锁越小竞争条件就越小。就像你买咖啡,把整个咖啡馆当做“竞争条件”或者把收银员当做“竞争条件”,很像然后者对咖啡馆的利用率更高。(还有一些买过咖啡的人至少可以逗留一会)

总结

我不想在文章中介绍Java的API,并发包的类。(这些介绍资料一抓一大把实在太多了)我的目的是通过回忆基础课程,尝试把我们认为最没用的东西联系到实际中。让大家知道——原来教科书上不是骗人的,真的是无处不在。希望你看完这篇文章,能够默默的去翻开尘封已久的《操作系统原理》。郭德纲老师有句话说:演员和演员最后比拼的是文化底蕴;同样的道理, 程序员和程序员最后比拼的是基础 ,当你羡慕别人“游刃有余”的轻松解决一个很难缠的BUG时;动动手指搞定解决了性能问题时;短时间内迅速领悟到一个新技术时,请不要忘记他在这个背后可能花了几年甚至几十年的功夫在练习被你遗弃的“教科书”。

最后推荐几本靠谱的操作系统书(不要浮躁,每本书都值得我们花上一年半载的慢慢欣赏)

  • 现代操作系统 我从未见过如此牛B的操作系统书,没有之一!!!用最通熟易懂的语言,最简单的例子解释最复杂的道理。最牛B的是,它还很现代,非常现代。
  • 计算机的心智:操作系统之哲学原理 邹恒明,上海交大的教授写的。本地书的特点就是薄,你喜欢看薄书这个是一个好选择。
  • Linux内核设计与实现,薄书。如果你想了解Linux Kernel有不想太深入,这本书绝对的适合你
时间: 2024-10-12 15:24:14

Java并发原理谈谈的相关文章

Java并发指南开篇:Java并发编程学习大纲

Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容. 这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类.当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中涉及到的技术原理十分丰富.为了更好地把并发知识形成一个体系,也鉴于本人没有能力写出这类文章,于是参考几位并发编程专家的博客和书籍,做一个简单的整理. 一:并发基础和多线程 首先需要学习的就是并发的基础知识,什么是并发,为什么要并发,多线程的概念,线程安全的概念等. 然后学会使用Java中的Threa

如何才能够系统地学习Java并发技术?

Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容. 这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类.当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中涉及到的技术原理十分丰富.为了更好地把并发知识形成一个体系,也鉴于本人目前也没有能力写出这类文章,于是参考几位并发编程方面专家的博客和书籍,做一个简单的整理. 首先说一下我学习Java并发编程的一些方法吧.大概分为这几步: 1.先学会最基础的Java多线程编程,Thread类的使用,线程通信的一些方

【死磕Java并发】-----深入分析volatile的实现原理

通过前面一章我们了解了synchronized是一个重量级的锁,虽然JVM对它做了很多优化,而下面介绍的volatile则是轻量级的synchronized.如果一个变量使用volatile,则它比使用synchronized的成本更加低,因为它不会引起线程上下文的切换和调度.Java语言规范对volatile的定义如下: Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量. 上面比较绕口,通俗点讲就是说一个变量如果用volatil

Java 并发编程:volatile的使用及其原理

Java并发编程系列[未完]: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) Java 并发编程:volatile的使用及其原理 一.volatile的作用 在<a href="http://www.cnblogs.com/paddix/p/5374810.html">&

Java并发编程底层实现原理 - volatile

Java语言规范第三版中对volatile的定义如下: Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致性的更新,线程应该确保通过排他锁 单独获得这个变量. volatile有时候比锁更加方便,比如一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的 值是一致性的. volatile是如何来保证可见性的?  需要查看Java代码转换成汇编代码之后,具体执行的过程可参考<Java并发编程的艺术> 第二章,或者其他资料.(主要是我对汇编不太熟) 还涉

Java并发编程:Synchronized及其实现原理

Java并发编程系列[未完]: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题.从语法上讲,Synchronized总共有三种用法: (1)修饰普通方法 (2)修饰静态方法 (3)修饰代码块 接下

Java并发机制及锁的实现原理

Java并发编程概述 并发编程的目的是为了让程序运行得更快,但是,并不是启动更多的线程就能让程序最大限度地并发执行.在进行并发编程时,如果希望通过多线程执行任务让程序运行得更快,会面临非常多的挑战,比如上下文切换的问题.死锁的问题,以及受限于硬件和软件的资源限制问题,本章会介绍几种并发编程的挑战以及解决方案. 上下文切换 即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制.时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行

Java并发编程原理与实战

Java并发编程原理与实战网盘地址:https://pan.baidu.com/s/1c3mpC7A 密码: pe62备用地址(腾讯微云):https://share.weiyun.com/11ea938c7ad43783a934ed1d492eed8d 密码:ogHukS 原文地址:http://blog.51cto.com/13406637/2071116

14套java精品高级架构课,Dubbo分布式Restful 服务,并发原理编程,SpringBoot,SpringCloud,RocketMQ中间件视频教程

14套java精品高级架构课,缓存架构,深入Jvm虚拟机,全文检索Elasticsearch,Dubbo分布式Restful 服务,并发原理编程,SpringBoot,SpringCloud,RocketMQ中间件,Mysql分布式集群,服务架构,运 维架构视频教程 14套精品课程介绍: 1.14套精 品是最新整理的课程,都是当下最火的技术,最火的课程,也是全网课程的精品: 2.14套资 源包含:全套完整高清视频.完整源码.配套文档: 3.知识也 是需要投资的,有投入才会有产出(保证投入产出比是