Java线程的详细解读

一:线程实现方式

Java线程的详细解读_第1张图片

词典

  • p:进程
  • LWP:轻量级进程
  • K: 内核线程

基于内核线程实现

       内核线程其实就是有操作系统内核支持的线程,这种线程有操作系统来负责线程的切换,内核通过操作调度器对线程进行调度;

       程序一般不会直接去使用内核线程而是去使用一种内核线程的高级接口(轻量级线程),轻量级线程就是我们通常所说的线程,每一个轻量级线程都有一个内核线程来支撑,因此轻量级线程和内核线程是一一对应的模型;

       局限性:

  1. 居于内核线程实现,线程的操作(创建,析构,同步)都需要内核支持系统开销大需要用户态和内核态来回切换
  2. 需要消耗内核资源(如内核的栈空间)

基于用户线程实现

       用户线程的建立、同步、销户和调度完全在用户态中完成,不需要内核的帮助;

如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的;

这种进程与用户线程之间1:N的关系称为一对多的线程模型

       优势:

  1. 不需要内核支持,系统开销小

局限性:

  1. 由于没有内核的支持,线程的所有操作(创建,析构,调度,同步)都需要自己来实现,复杂度非常高,基本不可实现

基于用户线程+轻量级进程混合实现

       用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。

轻量级进程作为桥梁,可以使用内核提供线程调度功能以及处理器映射功能,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。这种关系为N:M关系,多对多的线程模型

 

线程的创建过程

Java线程的详细解读_第2张图片

步骤

  1. 调用Java线程的Start方法,通过JNI调用到JVM层
  2. JVM通过pthread_create()创建一个系统内核线程,并指定内核线程的初始运行地址,即一个方法指针
  3. 在内核线程的初始运行方法中,利用JavaCalls模块,调用java线程的run()方法,开始java级别的线程执行

Java线程调度

线程调度是只操作系统给线程分配使用权限和使用时间,通常可以分为“抢占式调度” “协同式调度”两种方式

抢占式调度:

       每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行时间)。线程执行时间系统可控,也不会有一个线程导致整个进程阻塞;

 

协同式调度:

       线程执行时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。最大好处是实现简单,且切换操作对线程自己是可知的,没啥线程同步问题。坏处是线程执行时间不可控制,如果一个线程有问题,可能一直阻塞在那里;

 

线程的状态切换

Java线程的详细解读_第3张图片

  1. 运行状态
    1. Runnable包括操作系统线程状态中的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能等待CPU为它分配执行时间。线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得
  2. 阻塞状态(无限期等待,期限等待,Blocked)
    1. 无限期等待:该状态下线程不会被分配CPU执行时间,要等待被其他线程显式唤醒。如没有设置timeoutobject.wait()方法和Thread.join()方法,以及LockSupport.park()方法;
    2. 期限等待:不会被分配CPU执行时间,不过无须等待被其他线程显式唤醒,在一定时间之后会由系统自动唤醒。如Thread.sleep(),设置了timeout的object.wait()和thread.join(),LockSupport.parkNanos()以及LockSupport.parkUntil()方法;
    3. Blocked:线程被阻塞了。与等待状态的区别是:阻塞在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而等待则在等待一段时间,或唤醒动作的发生。在等待进入同步区域时,线程将进入这种状态;

 

线程间的协作

       常见的线程间通信有wait、notify、notifyAll、join、yield、sleep

  1. Join
    1. Join是Thread类的一个方法,作用是阻塞调用线程直到该线程结束, 意思是t.join()方法将阻塞调用此方法的线程直到t线程结束,此线程再继续执行, 通常用在main主线程内,等待其他线程执行完再结束主线程;
  2. Wait、notify、notifyAll
    1. Wait方法使线程进入等待状态,notify/notify唤醒线程的等待状态进入阻塞状态

列子:

 

Java线程的详细解读_第4张图片

Java线程的详细解读_第5张图片

Java线程的详细解读_第6张图片

执行结果

Java线程的详细解读_第7张图片

    1. 根据以上案例需要明白几个点
      1. 在示例中没有体现但很重要的是,wait/notify方法的调用必须处在该对象的锁(Monitor)中,也即,在调用这些方法时首先需要获得该对象的锁。否则会爬出IllegalMonitorStateException异常
      2. 从输出结果来看,在生产者调用notify()后,消费者并没有立即被唤醒,而是等到生产者退出同步块后才唤醒执行。(这点其实也好理解,synchronized同步方法(块)同一时刻只允许一个线程在里面,生产者不退出,消费者也进不去)
      3. 消费者被唤醒后是从wait()方法(被阻塞的地方)后面执行,而不是重新从同步块开头
  1. Yield
    1. sleep 方法使当前运行中的线程睡眠一段时间,进入不可以运行状态,这段时间的长短是由程序设定的,yield方法使当前线程让出CPU占有权,但让出的时间是不可设定的。
    2. yield()也不会释放锁标志。
    3. 实际上,yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为退让,它把运行机会让给了同等级的其他线程

参考文献

  • 一篇文章总结了JVM线程基本原理
  • 深入理解JVM-Java线程-实现方式,线程调度,状态
  • JVM之JAVA线程启动流程
  • JVM方法执行的来龙去脉

你可能感兴趣的:(高并发,线程,线程,进程,线程状态,线程协同,线程调度)