第一集:Java使用多线程实现并发编程理论篇

版权声明:作者:星云 交流即分享,分享才能进步!喜欢我的文章,可在博客左侧扫码赞赏~ https://blog.csdn.net/hadues/article/details/88998887

1. 并发和并行区别

什么是并行?什么又是并发?

1. 1 KTV唱歌例子

  • 并行 以KTV唱歌为例, 并行则是指有多少个人可以使用话筒同时唱歌
  • 并发 以KTV唱歌为例,并发指的是同一个话筒可以被多个人轮流使用。

1.2 医生坐诊

  • 并行 某个科室两个专家同时坐诊
  • 并发 一个医生,时而问诊,时而查看化验单,然后继续问诊,突然又中断去处理病人的咨询,这就是并发。

1.3 炒菜

  • 并行 七个厨师同时炒这七个菜,一人炒一个菜
  • 并发 一个人同时炒七个菜,第一个菜洗菜切菜锅里煮,然后停下来切第二个菜,再停下来,

总结:

  • 并行:多核处理器上多个任务同时执行
  • 并发:单核处理器上多个任务交替执行

2. 为什么要使用并发编程?

大家都知道,在程序的编程最初,我们都是使用顺序编程的知识。
可能有些人会打断问到,还不知道并发呢,顺序编程又是啥,能吃么?

2.1 什么是顺序编程?

所谓的顺序编程就是:

程序中的所有事物在任意时刻都只能执行一个步骤。

通俗点讲就是:写完所有代码,再去打游戏

2.2 什么是并发编程?

通俗点来将就是:写一会代码,打一会游戏,交替进行

2.3 为什么使用并发编程?

  • 更快的执行
  • 做更多的事(这句我自己加的)

比如,我经常在家做饭,一般需要煮面和炒菜,用时十五分钟。
我有三种选择,
第一种,先烧开水,煮面,再洗菜切菜炒菜。
第二种,先洗菜切菜炒菜,最后烧开水,煮面。
第三种,烧开水,等待的同时洗菜切菜,煮面,炒菜。

按照顺序编程的做法,无论选择第一种,还是第二种,由于烧水和煮面都是比较耗时的工作,效率都比较低下,可能需要耗时十五分钟。

但是如果使用第三种并发编程的做法,效率就比较高,比较省时间,可以十分钟完成这一切。

显而易见并发编程的一个好处就是在同一段时间内可以比顺序编程做更多的事。

但是值得注意的是并非所有的场景都适合使用并发编程。
从性能的角度上来看,如果任务没有阻塞,那么在单处理器上使用并发就没有任何意义,因为任务和任务之间切换也需要耗费时间。

也就是说并发编程是有前提的,那就是必须有阻塞

3. 进程和线程的关系

我们知道,计算机中所有程序的执行,首先都需要放到一段内存空间里,然后CPU从内存空间中取出数据,再通过寄存器之类操作,最终对比并更新操作的结果放到内存中。

传说中的CAS 是Compare-and-Swap的缩写,即先对比然后更新交换数据)。

  • 进程是系统进行资源分配和调度的基本单位。
  • 线程是CPU调度和分派的基本单位

也就是说进程可以直接获取操作系统给自己分配的内存空间,而线程不能由操作系统直接获取分配内存空间,只能使用进程分配到的内存空间。

操作系统会将线程和线程之间内存区域互相隔离开,因此Java并发要处理就是使用线程如何正确处理同步这些共享内存空间中的数据。

总结就是:一个操作性系统有多个进程,每个进程有多个线程。

4. 如何实现并发?

4.1 理论上最直接的方式是使用进程

理论上,实现并发最直接的方式是在操作系统级别使用进程。

因为在微软系统中,进程与进程之间就是并发的经典例子,
虽然看似听歌和写博客两个进程在同时进行,实际上只是操作系统的CPU快速切换两个线程使用CPU的时间,由于切换速度比较快,人类不容易察觉,所以产生的一种假象好像在同时进行一样。

4.2 为什么不使用进程实现并发编程?

实际上Java实现并发编程的方式并不是使用进程而是使用线程

那么为什么呢?

假设一台电脑主机内存一共8GB

由于每个进程都需要分配内存空间,每个进程需要1GB

那么整个操作系统最多只能有八个进程。

因此操作系统对进程有数量和开销的限制。

既然并发不能使用进程,那么怎么在线程级别实现并发编程呢?

4.3 Erlang使用多任务隔离处理并发编程

多线程存在的问题在于如何共享内存和I/O 中的数据,Erlang 这种专门针对多线程而生的语言,可以将并发任务彼此隔离。

也就是说,在Erlang 中的并发中线程与线程之间不会出现资源争抢的问题,因为他们的任务都是彼此隔离的。

解决思路:并发任务彼此隔离后就不存在资源共享的问题

4.4 为什么Java 不使用多任务隔离处理并发编程?

由于早期的苹果OSX系统,不支持多任务,如果在某些平台上也使用类似Erlang 那种语言的解决方案,就打破了Java “编写一次,到处运行”的要求。

多线程的含义:在单一进程中创建多个任务

解决思路:Java 使用顺序编程的基础上引入多线程实现并发编程

5. 多线程可能存在什么问题?

试看一个场景:

假如有
一个线程A
一个线程B
共享变量C

一个线程A 尝试修改C
一个线程B 尝试读取C

由于线程A 和线程B 先后顺序是不可控的,

因此,如果不加以控制,

那么线程A读到的C值就可能有两种

可能读取到的值要么是旧的值

也可能读取到的是修改后的值

总结出现的问题:

线程A和线程B之间由于共享了内存资源(变量C),
当一个线程A尝试修改共享的内存资源(变量C),
一个线程B尝试读取共享的内存资源(变量C)
线程A和线程B的执行先后顺序不固定,导致线程B有时候读到的是修改前的变量C的值,有时候读到的是修改后变量C的值。

6. 如何解决资源共享同步问题?

上面我们可以看到,多线程出现的核心难点问题就是

  1. 线程一和线程二之间共享了内存资源(有时候也可能是I/O资源)
  2. 线程一和线程二先后执行顺序不固定

此时我们可能会想:

既然线程的机制是抢占式的,

要是有一种方法能指定线程A和线程B执行的先后顺序,这个问题就解决了。

那么到底有没有这样一种方法呢?

答案是肯定的那就是使用协作多线程

在协作式系统中,每个任务都会自动放弃控制(都不主动获取变量C的值),这就需要我们在每个任务中插入某种类型的让步语句,比如通过加锁,同步代码块等方式,先让线程A执行,再让线程B执行。


好了,这篇博文就讲到这里。

如果没看饱,那么请继续。。。
第二集:创建线程的三种方法以及线程池的几种类型
第三集:并发编程一定比顺序编程速度更快么?
第四集:并发编程中为什么会出现死锁?
第五集:探究并发编程中volatile和synchronized关键字的含义和用法

猜你喜欢

转载自blog.csdn.net/hadues/article/details/88998887
今日推荐