干货-并发编程提高——线程的实现(四)

引言

  1. 计算机中,中央处理器的运算是最快的,但是大多数的运算都不能只依靠中央处理器独自完成。处理器至少需要与内存的交互。这种I/O操作是很难消除的。所以现代的计算机中都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存。来做为处理器与内存之间的缓冲。
  2. 高速缓存很好的解决了处理器与内存的速度矛盾。但是也带来了新的问题,缓存一致性问题。所以产生了缓存一致性协议。
  3. 为了使得处理器内部的运算单元尽可能的充分被利用。处理器可能会对输入的代码进行乱序执行优化。处理器会在计算机之后将乱序执行的结果重组。保证该结果与顺序执行的结果一样。如果存在一个计算任务依赖于另外一个计算任务的中间结果。那么其顺序性就不能靠代码的先后顺序来保证了。
  4. Java中的JIT编译器也有类似的指令重排序优化。

计算机中线程的实现:

  1. 使用内核线程实现<Kernel-Level Thread - KLT>,直接由操作系统内核支持的线程。这种线程由内核来完成切换。内核通过操作调度器对线程进行调度。并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身。程序一般不会直接去使用内核线程。而是去使用内核线程的一种高级接口。轻量级进程<Light Weight Process - LWP>。轻量级线程与内核线程之间是1:1的关系。由于内核线程的支持,每个轻量级进程都成为一个独立的调度单元。即使有一个轻量级的进程在系统调度中阻塞了。也不会影响整个进程继续工作。由于是基于内核线程的实现。所以各种线程操作,如创建,析构及同步都需要进行系统调用。而系统调用的代价相对较高。需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换。每个轻量级进程都需要有一个内核线程的支持。因此轻量级进程要消耗一定的内核资源。因此一个系统支持轻量级进程的数量是有限的。
  2. 使用用户线程实现,只要不是内核线程,就可以认为是用户线程。是不是有点儿像类加载器?用户线程的建立,同步,销毁调度完全在用户态中完成。不需要内核的帮助。如果程序实现得当。这种线程不需要切换到内核态,因此操作是可以非常快速且低消耗的完成的。也可以支持更大规模的线程数。使用用户线程的优势是不需要系统内核支援。劣势也是在没有系统内核的支援。所有的线程操作,都需要用户程序自己处理。线程的创建,切换,调度都是需要考虑的问题。
  3. 用户线程加轻量级进程的混合实现,即存在于用户线程又存在于轻量级进程。用户线程可以完全建立在用户空间中,因此用户线程的创建,切换,析构都操作依赖廉价。并且可以支持大规模的用户线程并发。而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁。这样可以使用内核提供的线程调度功能及处理器映射。并且用户线程的系统调用要通过轻量级线程来完成。极大的降低了整个进程调度功能被完全阻塞的风险。
  4. Java线程的实现是基于称为"绿色线程"的用户线程实现的。一条java线程映射到一条轻量级进程之中。而在Solaris平台中,操作系统线程可以同时支持一对一,及一对多的线程模型。
  5. 线程切换,阻塞的过程是很耗费CPU资源的。举例:AQS添加链表。
  6. Yeild方法,让出CPU的执行时间。依旧回退到就绪状态(Runable)。能让不能得。就比如有个人骑着一辆自行车,调用Yeild方法后就从自行车上下来了。此时此刻有很多人盯着这辆自行车。但是此人也可能手脚麻利 又再次获得此自行车。所以并不可靠。
  7. Join方法底层是使用wait方法实现的,所以调用join方法,会释放锁。Join方法形同骑着一辆自行车,然后找到指定的人,跟他说,你骑吧,我睡儿会。哪个线程执行的x线程join方法,哪个线程阻塞。等到x线程执行完才会被唤醒。Join方法并不需要在同步块儿中执行。
  8. wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写,相应的sleep方法是可以被重写的,如果你手贱的话。
  9. wait()方法使当前线程处等待状态,wait方法必须先持有锁。其实也就对应了wait方法必须在同步块儿中执行,当然在同步块儿中了,也就获得了锁。
  10. 如果线程处于New状态,并未启动,此时调用它的join方法是不起作用的。程序将继续向下执行。因此时被调用的线程还未准备好(非就绪状态Runable)。
  11. 可以使用线程的join方法保证执行顺序。相当于一个递归操作。
    1. Thread05->Thread04.join->Thread03.join->Thread02.join->Thread01.join
    2. 实则执行的顺序是01->02->03->04->05
  12. 我们调用wait方法时是必须要在同步体里的,然而join方法本身就是由synchronized所修饰的。参看下方:所以调用join方法时就已经获取到了t1对象的锁。在主线程中调用了t1的join方法,代表主线程获取到了t1对象的锁,参看join方法的源码得知。此时内部的wait方法便是将主线程置为等待状态。
    1. 参看:Thread t1 = new Thread(); Thread t2 = new Thread(); t1.start(); t2.start(); t1.join();
相关推荐
冰之杍20 分钟前
Vscode进行Java开发环境搭建
java·ide·vscode
skaiuijing1 小时前
Sparrow系列拓展篇:消息队列和互斥锁等IPC机制的设计
c语言·开发语言·算法·操作系统·arm
雯0609~2 小时前
c#:winform调用bartender实现打印(学习整理笔记)
开发语言·c#
胜天半子_王二_王半仙3 小时前
c++源码阅读__smart_ptr__正文阅读
开发语言·c++·开源
沐泽Mu4 小时前
嵌入式学习-C嘎嘎-Day08
开发语言·c++·算法
Non importa4 小时前
汉诺塔(hanio)--C语言函数递归
c语言·开发语言·算法·学习方法
LinuxST4 小时前
27、基于Firefly-rk3399中断休眠唤醒实验(按键中断)
linux·开发语言·stm32·嵌入式硬件
Tony_long74834 小时前
Python学习——猜拳小游戏
开发语言·python·学习
跳动的梦想家h4 小时前
黑马点评 秒杀下单出现的问题:服务器异常---java.lang.NullPointerException: null(已解决)
java·开发语言·redis
苹果醋34 小时前
前端面试之九阴真经
java·运维·spring boot·mysql·nginx