专栏首页步履前行java 并发篇- 概念篇

java 并发篇- 概念篇

各位好,今天是我们并发篇正式开始的第一篇,既然我们大家学习并发,那么就要理解一些计算机概念最好,否则,知道怎么用而不知道名称是啥,概念含糊不清,以及不知道怎么设计的,假如今天你突然换 go 语言,设计个并发还是不会。我们要学的是并发思想,在Java 中的思想,一通则百通,而不是背代码,切记切记。


让我们用面向对象的方法,把我们日常中的房子做为对象来解释线程和进程的工作。

房子首先是一个装东西的容器。它本身有自己的属性 -- 面积,卫生间数量,卧室数量,客厅,书房等等。

房子是被人住的,所以它是一个被动方。

作为房子的使用者 - 人,就是操作方,使用电视,做饭,洗澡,撒狗粮。都属于行为了。

单线程

如果是一个人在家里,那么你可以想干啥就干啥,毕竟我得地盘我做主。

多线程

假如你结婚了,那么就不能随自己心愿了,比如,你的另一半在厕所,那么你就不能进去了,因为毕竟被别人占据着。 当然,因为你们是2个人,所以理论上家里会相对于一个人更加安全,比如煤气泄漏,房门上锁等(当然你一个人也可以),这个称为理论上安全(另一个要和你一起去GG,这不就不安全了呗)。

但是,结婚多年的你们擦枪走火,造出了熊孩子,这时候各种破坏,你们这个家就非常的戏剧性了。

回到计算机

因为你和你的另一半是房子的主人,那么自然可以随意的进出房间,这时如果你去买了一台电脑,你和你的另一半可以去使用它,(电脑在房间里,属于公共资源),你和你的另一半就是线程了,你们想要使用电脑就需要排队了,这就是线程的阻塞了,(当然如果不作死的话,那么理论上女方应该拥有更大的使用权,也就是不公平锁,女方的权重更大,当然公平锁也是可以的,只要不怕老婆削你就ok。)

但是如果不想让熊孩子(线程)去玩电脑(公共资源),那么只要把电脑远离,熊孩子自然不能玩了,此时电脑对于熊孩子来说就是不可访问的权限。

互斥

如果你想使用卫生间,但是有人占据着,那么这个时候理论上你不可以进去,这就是互斥。(因为你要使用公共资源) 别人使用卫生间的时候是从内部上锁(Lock),使用完在解锁(Lock)开门,这个时候你才能使用,卫生间。

但是如果你们家卫生间设计比较独特的话,比如 卫生间的门上有 2 把锁(鬼知道你设计 2 把锁要干嘛,反正都是锁门),那么你把 2 把锁都锁上的话,其实我们就可以看成是 可重入锁了(暂时没想到现实中有什么锁是要锁2次,抱歉)

优先级 & 公平性

早上起床晚了,马上要上班,然后你另一半在卫生间洗漱,而你还好巧不巧的还拉肚子,但是你只能先忍住等着,等你另一半出来,还是阻塞。但是等 他/她 开门后会怎么办,谁接着去呢(如果正巧她还要化妆呢)

如果你已经等了好久了,那么在 另一半出来后,在公平的情况下,应该是你先进去才对,毕竟你等了很长时间了,但是在不公平的情况下呢,那么就要按照优先级了,因为你马上上班迟到,还拉肚子,你是最着急的,而化妆是可以暂缓的,所以你拥有较高的优先级。

线程也一样。线程从其父线程继承其调度算法,但可以调用 pthread_setschedparam() 来更改其调度策略和优先级(如果它有权执行此操作)。

优先级高只是一个建议,比如Java中的高优先级,并不一定会对照 操作系统的优先级,如果操作系统的优先级级大于java的优先级级(10级)还好,要是小于得的话就不行了,这样会导致不同优先级的线程的优先级是一样的。而且 windows 还有一个 优先级推进器,如果一个高优先级的线程执行过多次数,那么会越过优先级来分配。


信号量

  • 信号量为 1 的 我们还是以卫生间为例,其中卫生间的门只有 2 种状态:
    • 门开着,证明没有人在里面
    • 门锁着,证明有人在里面
  • 信号量 大于 1 的 比如家里的钥匙有 2 把。任何拿到钥匙的人都可以打开门进去,当进入到房子里面时,总会锁门,那么外面进入的人总要需要钥匙来开锁。所以如果我们想要控制进入房子里的人,只要控制钥匙就OK了(配钥匙的不算哈。)

线程中就可以使用信号量了,进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。如果多个线程访问同一资源,那么大于 1 的信号量就是允许同一时间的线程通过量了。


内核

  • 内核作为仲裁者

内核确定在特定时刻由哪个线程使用 CPU。并将上下文切换到该线程。所以这里就是个性能问题。

内核切换线程时,需要:

  • 保存当前运行的线程的寄存器和其他上下文信息
  • 将新线程的寄存器和上下文加载到CPU中

内核如何决定另一个线程应该运行?

它会查看特定线程是否能够在此时使用CPU。当遇到互斥锁时,会有一个阻塞状态。我们有一个可以消耗CPU的线程,而另一个不能,因为它被阻塞,等待互斥锁。在这种情况下,内核允许可以运行的线程消耗CPU,并将另一个线程放入内部列表(以便内核可以跟踪其对互斥锁的请求)。


如果有多个线程使用CPU的时候,到底该怎么委派呢?

内核使用优先级和调度算法来评估。

  • 优先级 多个线程中,如果线程具有不同的优先级,那么很简单,内核将 CPU 提供给优先级最高的线程。如果有同一时间一个更高优先级的线程突然变的能够使用 CPU ,那么内核将立即上下文切换,也就是抢占;当高优先级的线程使用完毕后,内核将切换回之前的比较低的线程,这个时候称之为恢复;

但是如果有两个线程有相同的优先级时,又该怎么办呢?请看下方:

  • 调度算法

让我们继续假设,如果一个线程正在使用 CPU,此时内核将检查进行上下文切换的规则(优先级不同的,依然以上面的为主)。此时Linux就要使用调度算法了, Linux 2.6 之后 中主要有楼梯调度算法,CFS (完全公平调度算法) 以及 RSDL(由于内核调度是个大概念,我们这里不再叙述,有兴趣的同学可以下去研究下)。任何时候,实时进程的优先级都高于普通进程,实时进程只会被更高级的实时进程抢占。

线程状态

  • 内核状态
    • 就绪(ready)
    • 执行(running)
    • 挂起(blocked)
  • Linux 进程状态
  • RUNNING

进程正在执行或等待执行

  • INTERRUPTIBLE

进程在休眠模式下等待中断,并等待某些操作可以唤醒此进程。

  • UN-INTERRUPTIBLE

和 INTERRUPTIBLE 相同,只是这个状态的进程无法通过传递信号唤醒

  • STOPPED

进程已停止

  • TRACED

此状态表示正在调试进程

  • ZOMBIE

程已终止但仍在内核进程表中闲置,此进程的父进程仍未获取此进程的终止状态

  • DEAD

终止进程并从进程表中删除条目


  • Java 线程状态
    • NEW(初始化状态)
    • RUNNABLE(可运行 / 运行状态)
    • BLOCKED(阻塞状态)
    • WAITING(无时限等待)
    • TIMED_WAITING(有时限等待)
    • TERMINATED(终止状态

    Linux 进程 & 线程

  • Linux 进程

Linux进程可以被视为运行程序的实例。进程可以使用进程间通信方法与其他进程通信,并可以使用共享内存等技术共享数据。

  • Linux线程

Linux中的线程只是流程的执行流程。包含多个执行流程的流程称为多线程流程。非多线程进程,只有执行流程是主要的,所以也被称为单线程进程

  • 一个整整想了2天的问题, --- Linux 中的线程到底是什么

查询了好多资料,发现好多博客都在说 Linux 不支持线程,只是进程啥的,然后也没有给到具体的理由。我就去内核官网 以及一些 Linux 爱好者博客中找了下,下面是我自己的理解:

在开始的时候 ,也就是 Linux 2.6 版本之前,没有线程,只有单独的进程(具有单独的PID)来抽象线程,这种进程可能具有一些共享资源,如虚拟内存或文件描述符。而这就导致了臭名昭著的LinuxThreads POSIX线程的实现,这是一个误称,因为它没有给出任何与POSIX线程语义类似的东西。

之后 linuxThreads 被 NPTL 取代了,实现发生了变化,NPTL的解决方法与LinuxThreads类似,内核看到的首要抽象依然是一个进程,这种进程称为轻量级进程,新线程是通过clone()系统调用产生的。但是NPTL需要特殊的内核支持来解决同步的原始类型之间互相竞争的状况。在这种情况下线程必须能够入眠和再复苏。

NPTL是一个所谓的1×1线程函数库。用户产生的线程与内核能够分配的对象之间的联系是一对一的。这是所有线程程序中最简单的。。 。轻量级进程(LWP)和正常进程之间的主要区别在于LWP共享相同的地址空间和其他资源(如打开文件等)。由于某些资源是共享的,因此与其他正常进程相比,这些进程被认为是轻量级的因而名称轻量级过程。

在Linux中。内核线程本质上是没有用户空间的进程。用户空间线程是正常的POSIX线程(NPTL)。用户空间进程共享文件描述符,可以共享代码段, 但生活在完全独立的虚拟地址空间中。进程内的用户空间线程共享代码段,静态内存和堆(动态内存),但具有单独的处理器寄存器集和堆栈


这篇我们主要介绍了一些之后会用到的一些基本概念。下篇我们将开始细讲导致我们并发 bug 的三大因素 - 可见性、原子性、有序性 以及 Java 内存模型

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • RabbitMQ消息中间件技术精讲7 发送自定义属性消息

    Auto Delete:如选yes,代表当最后一个监听被移除之后,该Queue会自动被删除

    凯哥Java
  • Win7 Eclipse 搭建spark java1.8环境:WordCount helloworld例子

    Win7 Eclipse 搭建spark java1.8环境:WordCount helloworld例子 马克-to-win @ 马克java社区:在ecli...

    马克java社区
  • Scala-13.包和导包

    悠扬前奏
  • java程序打包后JAR后运行特别慢原因

    版权声明:本文为博主原创文章,转载请保留出处 ...

    xyjincan
  • 【转载】完全理解Python迭代对象、迭代器、生成器

    在了解Python的数据结构时,容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合...

    marsggbo
  • 比较java c++ 对象之间的区别(内存)

    版权声明:本文为博主原创文章,转载请保留出处 ...

    xyjincan
  • TCP 长连接小尝试

    版权声明:本文为博主原创文章,转载请保留出处 ...

    xyjincan
  • 聊聊feign的Retryer

    feign-core-10.2.3-sources.jar!/feign/Retryer.java

    codecraft
  • Win7 Eclipse 搭建spark java1.8编译环境,JavaRDD的helloworld例子

    Win7 Eclipse 搭建spark java1.8编译环境,JavaRDD的helloworld例子:

    马克java社区
  • nginx+uWsgi配置问题的解决

    uWSGI 是在像 nginx 、 lighttpd 以及 cherokee 服务器上的一个部署的选择。更多选择见 FastCGI 和 独立 WSGI 容器 。...

    习惯说一说

扫码关注云+社区

领取腾讯云代金券