本节课来了解一个非常重要的问题,线程安全性问题。在了解线程安全性问题之前,我们先了解一下关于线程所带来的风险这个问题,
我们之前也提到过这么一回事,我们说,线程不光有它的优势,同样的,线程也会带来一定的风险,我们当时提到了三点,第一点就是线程安全性问题,第二个是活跃性问题,第三个是性能问题。从第一点到第二点线程带来的风险严重性依次降低,第三个其实就是相当于优化。
线程安全性问题是我们往后很多节的重点。我们会先了解什么是线程安全性问题,然后,我们该如何解决这个问题,那么,解决的过程中就会涉及到各种的锁、同步监视器等等,我们就会对它们进行深入的研究。线程安全性问题是一块大的专题,所以,我们在解决线程安全性问题之前,我们先把第二点和第三点这两个问题先解决了。一个是活跃性问题,一个是性能问题。
什么是活跃性问题呢?活跃性问题有多种形式,比如,死锁就是一种活跃性问题的体现。再比如,饥饿、活锁等等,这都是活跃性问题的一种形式,
我们来分别说一下这三个。
死锁有一个非常经典的就是所谓的哲学家就餐的问题,说,有五个哲学家,每个哲学家在吃饭的时候,分给他们每人一只筷子,每个人只有一只筷子,我们知道,吃饭都需要用一双筷子,那么,这些哲学家,他们在谈论问题的时候,在探讨哲学问题的时候,可能有些哲学家就饿了,这些饿了的哲学家就开始吃饭,于是就借用他旁边的人的筷子,就组成了一双筷子,就可以吃饭了,这种情况下是正常的,但是,比如说,我们的程序结果设计成了,每个人都不聊哲学问题了,都在那里吃饭,于是,每个人都拿起了自己手中的那双筷子,再等待着另外一个人放下筷子,结果,每个人都不放下自己手中的筷子,结果,这五个哲学家最终就饿死了。这就是所谓的死锁问题,就是说,另外一个人手中有这个人所需要的资源,而另外一个人又有这个人手中所需要的资源,但是,这两个资源他们两个人都不释放,所以,他们两个人都拿不到自己需要的资源,这就是死锁问题,关于死锁问题,我们后面还会进行详细的说明。这里只是先给大家死锁这么一回事,就是,都需要对方的资源,但都不释放,这就会遇到死锁。
下面说饥饿问题,什么是饥饿呢?之前给大家举过一个例子,例子就是餐厅排队吃饭,只有一个打饭窗口,有很多人来排队打饭,但是这些人非常没有素质,非常没有礼貌,来了就插队,硬挤,而且买到了饭之后也不离开打饭窗口,可能就有那么一个弱小的同学,他死活就是挤不进去,不能打饭,于是就被饿死了。这就是饥饿。
在我们的线程中,其实就是,线程有优先级这么一个概念,有的线程的优先级高,有的线程的优先级低,那么,优先级低的线程,它就有可能一直得不到CPU的资源,也就是所谓的饥饿问题。
活锁,其实给大家举一个非常简单的例子,这个相当于,两个人,这两个人非常的有礼貌,这两个人在独木桥相遇了,(假设一条河上面有两座桥)这两个人非常的有礼貌,握了一个手,说,“不好意思”,然后就退回去了,另一个人也不好意的退回去了,于是,选择了另外的一条路,结果两个人又都在另一座桥上相遇,同理,一直这样下去。
于是就一直反反复复。这就是活锁问题。
我们发现,其实这三个问题还是非常严重的,死锁相对来讲,还是比较好检测到的。
jconsole这个工具就能够给我们提供一个检测死锁的工具,
当它检测到死锁的时候,它能够给我们提示,也就是说,死锁相对来讲还是比较好检测的,但是,对于饥饿和活锁,就不太好检测了。
好了,这是关于活跃性问题,后面我们针对于具体的,我们还会再说。
下面说关于性能问题。
性能问题,我们之前也提到过,而且我们还专门开了一讲来说这个事情,就是《多线程一定会快吗》这一讲。其实我们之前也提到过了,多线程执行并不一定会快,为什么呢?因为,多个线程并不一定是运行在多核的处理器上或者是多个处理器上,单核的处理器也是可以运行多个线程的,那么,我们就像之前烤烧饼一样,就是说,我们在画一下烤烧饼那张图
这是一个炉子。炉子上面放的是烧饼
炉子下面又火炉在烤
炉子在不停的转动。
那么,对应到我们的计算机中来看,就是说,火炉对应的就是CPU,CPU会为多个任务分配时间片,有一个概念叫做时间片,那么,什么是时间片呢?这个时间片就是CPU分配给各线程的时间,那么,这个时间片分配的时间是非常短的,所以,就是说,CPU会给每一个任务分配一个非常短的时间片,那么,当目前这个任务在这个时间片中执行完毕之后,CPU会去执行下一个任务,同理接着再执行下一个任务,因为这个时间片非常的短,所以,我们看起来这些任务是一起执行的,也即是说相当于炉子转的非常的快,那么,在进行任务的切换的过程中,其实就是称之为上下文切换,CPU通过一定的算法来进行循环执行这些任务,那么,当这个任务执行完毕之后,就是说,当前这个任务这个时间片执行完毕之后,会切换到下一个任务,但是在切换前,我们知道,比如炉子旋转一圈后,还要再执行这个任务,在这个时间片内这个任务可能还没有执行完毕,所以,我们在切换的时候,要保存当前这个任务的执行状态,以便到下一次再执行这个任务的时候,好能够加载出来这个任务状态继续执行,所以,在这个过程中,其实上下文切换是非常浪费我们的CPU资源的。
再举一个非常经典的例子,就是,比如说我们在查阅一本英文资料的时候,来个真实的例子,比如说我们想看sping的资料
我们来到这个网页上之后,我们发现,很多东西我都不认识,比如说我不认识Supports这个英文单词,我翻阅了一本英文词典查了一下,这个单词
然后我回到那个网页,找到Supports这个地方,才可以继续往下读。也就是说,当我在查阅英文单词的时候,我需要记住我在刚刚读的网页中读到了哪一页的哪一个地方了,然后,我再来翻开这本词典查询这个英文单词,然后,再对到网页中翻到刚刚读到的位置,这就是所谓的上下文切换。我们知道,显然不如直接在网页中往下读快。这就是性能问题。
关于线程带来的风险的活跃性问题和性能问题,就解释完毕了。那么,关于线程安全性问题,下一讲再来说。