前言

我们都知道,线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源调度(内存地址、文件I/O等),又可以独立调度。

线程的实现

主流的操作系统都提供了线程实现,Jav语言则是提供了在不同硬件和操作系统平台下对线程操作的统一处理,每个已经调用过start()方法且还未结束的java.lang.Thread类的实例就代表这一个线程。

其实Thread类与大部分的Java类库API有着显著差别,它的所有关键方法都被声明为Native。在Java中,一个Native方法往往就意味着这个方法没有使用或无法使用平台无关的手段来实现(通常最高效率的手段就是平台相关的手段)。

那么线程的实现其实是有三种方式的:

  • 使用内核线程实现(1:1实现);
  • 使用用户线程实现(1:N)实现;
  • 使用用户线程加轻量级进程混合实现;

内核线程实现

使用内核线程实现的方式被称为1:1实现。内核线程(Kernel Levvel Thread,KLT)就是直接由操作系统内核(Kernel,下称内核)支持的线程,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。

其实程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常所讲的线程。这种轻量级进程与内存线程之间1:1的关系称为一对一的线程模型



轻量级进程也具有它的局限性:首先,由于是基于内核线程实现的,所以各种线程操作(创建、析构及同步),都需要进行系统调用。系统调用就要在用户态和内核态中来回切换。其次,每个轻量级进程都需要一个内核线程的支持,因此需要消耗一定的内核资源,所以一个系统支持轻量级进程的数量是有限的。

用户线程实现

使用用户线程实现的方式被称为1:N实现。广义上来讲,一个线程只要不是内核线程,都可以任务是用户线程(User Threa,UT)的一种。从定义上来看轻量级进程不是内核线程也就是属于用户线程,但是它始终是建立在内核之上的,所以效率会受到限制,并不具备用户线程的优点。

用户线程的建立、同步、销毁和调度完全咋用户态中完成,不需要内核帮助。如果程序实现得当,不需要切换内核态,因此操作可以是非常快且低消耗的,也能够支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。

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



用户线程的速度快低消耗等优势在于不需要系统内核支援,但是劣势也在于没有内核的支援,所有的线程操作都需要由用户程序自己去处理。这样就会导致线程的一些问题处理起来就很困难,甚至有些是不可能实现的。

Java、Ruby等予以都曾经使用过用户线程,最终又都放弃了使用它。

混合实现

线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式,被称为N:M实现。

用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来完成,大大降低了整个进程被完全阻塞的风险。

Java线程的实现

Java线程如何实现并不受Java虚拟机规范约束,这是一个与具体虚拟机相关的画图。Java线程在早期的Classic虚拟机上(JDK1.2以前),是基于一种被称为“绿色线程”(Green Threads)的用户线程实现的,但从JDK1.3起,“主流”平台上的“主流”商用Java虚拟机的线程模型普遍都被替换为基于操作系统原生线程模型来实现,即采用1:1的线程模型。

操作系统支持怎样的线程模型,在很大程度想会影响上面的Java虚拟机的线程是怎么样映射的,这一点咋不同的平台上很难达成一致,因此《Java虚拟机规范》中才不去限定Java线程需要使用哪种线程模型来实现。

Java线程调度

线程调度是指系统为线程分配处理使用权的过程,调度主要方式有两种,分别是协同式(Cooperative Threads-Scheduling)线程调度抢占式(Preemptive Threads-Scheduling)线程调度

  • 协同式线程调度:线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另外一个线程上去。

    优点:实现简单,切换操作对线程自己是可知的,所以一般没有什么线程同步问题。

    缺点:线程执行时间不可控制,甚至如果一个线程的代码编写有问题,一直不告知系统进行线程切换,那么程序就会一直阻塞在那里。
  • 抢占式线程调度:每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。

    优点:可以主动让出执行时间(例如Java的Thread::yield()方法),并且线程的执行时间是系统可控的,也不会有一个线程导致整个系统阻塞的问题。

    缺点:无法主动获取执行时间。

Java使用的就是抢占式线程调度,虽然这种方式的线程调度是系统自己的完成的,但是我们可以给操作系统一些建议,就是通过设置线程优先级来实现。Java语言一共设置了10个级别的线程优先级。在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。

不过由于各个系统的提供的优先级数量不一致,所以导致Java提供的10个级别的线程优先级并不见得能与各系统的优先级都一一对应

Java线程状态转换

Java语言定义了6种线程状态,在任意一个时间点钟,一个线程只能有且只有其中的一种状态,并且可以通过特定的方法在不同状态之间切换。

  • 新建(New):创建后尚未启动的线程处于这种状态。
  • 运行(Runnable):包括操作系统线程状态中的Running和Ready,也就是处理此状态的线程有可能正在执行,也有可能正在等待着操作系统为它分配执行时间。
  • 无限期等待(Waiting):处于这种状态的线程不会被分配处理器执行时间,它们要等待被其他线程显示唤醒。

    以下方法会让线程陷入无限期等待状态:

    1、没有设置Timeout参数的Object::wait()方法;

    2、没有设置Timeout参数的Thread::join()方法;

    3LockSupport::park()方法。
  • 限期等待(Timed Waiting):处于这种状态的线程也不会被分配处理器执行时间,不过无须等待被其他线程显式唤醒,在一定时间之后它们会由系统自动唤醒。

    以下方法会让线程进入限期等待状态:

    1Thread::sleep()方法;

    2、设置了Timeout参数的Object::wait()方法;

    3、设置了Timeout参数的Thread::join()方法;

    4LockSupport::parkNanos()方法;

    5LockSupport::parkUntil()方法;
  • 阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是“阻塞状态”在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间 ,或者唤醒动作发生。在程序进入同步区域的时候,线程将进入这种状态。
  • 结束(Terminated):已终止线程的线程状态,线程已经结束执行。

这6种状态在遇到特定事件发生的时候将会互相转换,他们的转换关系如下图:

深入理解JVM(③)线程与Java的线程的更多相关文章

  1. 从 JVM 视角看看 Java 守护线程

    Java 多线程系列第 7 篇. 这篇我们来讲讲线程的另一个特性:守护线程 or 用户线程? 我们先来看看 Thread.setDaemon() 方法的注释,如下所示. Marks this thre ...

  2. 深入理解JVM(③)再谈线程安全

    前言 我们在编写程序的时候,一般是有个顺序的,就是先实现再优化,并不是所有的牛P程序都是一次就写出来的,肯定都是不断的优化完善来持续实现的.因此我们在考虑实现高并发程序的时候,要先保证并发的正确性,然 ...

  3. 【深入理解JVM】:Java对象的创建、内存布局、访问定位

    对象的创建 一个简单的创建对象语句Clazz instance = new Clazz();包含的主要过程包括了类加载检查.对象分配内存.并发处理.内存空间初始化.对象设置.执行ini方法等. 主要流 ...

  4. 【深入理解JVM】:Java内存模型JMM

    多任务和高并发的内存交互 多任务和高并发是衡量一台计算机处理器的能力重要指标之一.一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per Second,TPS)这个指标 ...

  5. 深入理解JVM(③)Java的锁优化

    前言 从JDK5到JDK6HotSpot虚拟机开发团队花费了大量的资源实现了各种锁优化技术,如适应性自旋(Adaptive Spinning).锁消除(Lock Elimination).锁膨胀(Lo ...

  6. 外部线程停止Java子线程的方法

    一.Thread.stop()官方不推荐,Because it is inherently unsafe. 二.方式一1. 线程类示例 public class ThreadT1 implements ...

  7. 【深入理解JVM】:Java类继承关系中的初始化顺序

    尝试着仔细阅读thinking in java 看到一篇很好的文章http://blog.csdn.net/u011080472/article/details/51330114

  8. 深入理解JVM(③)Java模块化系统

    前言 JDK9引入的Java模块化系统(Java Platform Module System ,JPMS)是 对Java技术的一次重要升级,除了像之前JAR包哪有充当代码的容器之外,还包括: 依赖其 ...

  9. 深入理解JVM(③)Java的模块化

    前言 JDK9引入的Java模块化系统(Java Platform Module System ,JPMS)是 对Java技术的一次重要升级,除了像之前JAR包那样充当代码的容器之外,还包括: 依赖其 ...

随机推荐

  1. PHP配合JS导出Excel大量数据

    一般使用PHP导出Excel表格都会用PHPExcel,但是当遇到要导出大量数据时,就会导致超时,内存溢出等问题.因此在项目中放弃使用这种方式,决定采用前段生成Excel的方式来解决问题. 步骤如下: ...

  2. Ehab and a 2-operation task【数论思想】

    Ehab and a 2-operation task 题目链接(点击) You're given an array aa of length nn. You can perform the foll ...

  3. foreach 集合又抛经典异常了,这次一定要刨根问底

    一:背景 1. 讲故事 最近同事在写一段业务逻辑的时候,程序跑起来总是报:集合已修改:可能无法执行枚举操作,硬是没有找到什么情况下会导致这个异常产生,就让我来找一下bug,其实这个异常在座的每个程序员 ...

  4. 网页元素居中的n种方法

    导语:元素居中对齐在很多场景看上去很和谐很漂亮.除此之外,对于前端开发面试者的基础也是很好的一个考察点.下面跟着作者的思路,一起来看下吧. 场景分析 一个元素,它有可能有背景,那我要它的背景居中对齐 ...

  5. tomcat的安装部署(windows10)

    一.Tomact下载 地址:https://tomcat.apache.org/

  6. 用turtle画蛇

    import turtle def drawSnake(rad,angle,len,nackrad): for i in range(len): turtle.circle(rad,angle) #画 ...

  7. 附024.Kubernetes全系列大总结

    Kubernetes全系列总结如下,后期不定期更新.欢迎基于学习.交流目的的转载和分享,禁止任何商业盗用,同时希望能带上原文出处,尊重ITer的成果,也是尊重知识.若发现任何错误或纰漏,留言反馈或右侧 ...

  8. SpringCloud教程第2篇:Ribbon(F版本)

    一.ribbon简介 Spring cloud有两种服务调用方式,一种是ribbon+restTemplate,另一种是feign.在这一篇文章首先讲解下基于ribbon+rest. ribbon是一 ...

  9. 工业4.0:换热站最酷设计—— Web SCADA 工业组态软件界面

    前言 随着工业4.0的不断普及与发展,以及国民经济的飞速前进,我国的城市集中供热规模也不断扩大,科学的管理热力管网具有非常重大的经济和社会效益.目前热力系统,如换热站大都采用人工监控,人工监控不仅浪费 ...

  10. 有return的情况下try_catch_finally的执行顺序

    java异常处理之try_catch_finally 看下面的一个列子: public class TestException { int goabl=1; public TestException( ...