知识回顾

进程与线程是常常被提到的两个概念。进程拥有独立的代码段、数据空间,线程共享代码段和数据空间,但有独立的栈空间。线程是操作系统调度的最小单位,通常一个进程会包含一个或多个线程。多线程和多进程都可以实现并发处理,如 nginx 使用多进程方式、tomcat 使用多线程方式、Apache 支持混合使用。在 C/C++ 等语言中可以同时使用多进程和多线程,而在 Java 中只能使用多线程。

在 Java 中,创建线程的唯一方式是创建 Thread 类的实例,调用实例的 start() 方法启动线程。

Java 线程实现

在 JDK 1.2 之前,Java 使用用户线程实现 Java 线程,在 JDK 1.2 及之后,Java 基于操作系统原生的线程模型实现 Java 线程。

使用用户线程( User Thread, UT ) 实现,是指线程建立在用户态空间,线程的建立、同步、调度与销毁都在用户态完成,进程与用户线程之间是1 : N 的对应关系。这种情况下,内核无法知道有多少个用户线程,实现较为复杂。

使用内核线程实现,是指基于轻量级进程( Light Weight Process, LWP ) 来实现线程。每个轻量级进程都有一个内核线程( Kernel-Level Thread, KLT ) 支持,与内核线程之间是 1 : 1 的对应关系。这种情况下,调度线程时可能需要在内核态和用户态之间进行切换。由于轻量级进程需要消耗内核资源,能够支持的线程数量是有限的。

如在 Windows 和 Linux 系统中,操作系统原生的线程模型是 1 : 1 的对应关系,对于 Sun JDK 来说,一个 Java 线程就对应着一个轻量级进程。

线程调度与状态

在 Java中线程的调度方式是抢占式调度,即由系统来负责各个线程的时间分配,并在线程使用完分配的时间后调度下一个线程。任何一个线程都不能独占 CPU 。Java 语言一共设置了 10 个线程优先级,当两个线程同时等待执行时,优先级高的先被调度。线程的优先级会被映射到操作系统原生线程上去,但各个操作系统的优先级划分不完全一样,因此两个优先级不同的 Java 线程在操作系统中执行时也可能处于相同的优先级。

Java 定义了 5 种线程状态,分别是新建 ( New )、运行 ( Running )、等待 ( Waiting )、限期等待 ( Timed Waiting )、阻塞 ( Blocked ) 和结束 ( Terminated )。任一时刻,线程都处于 5 种状态中的一种,并在各个状态之间切换,如图所示。

其中,各个状态含义如下:

新建:创建后未启动;

运行:对于 Java 来说,线程已经运行,但对于操作系统来说,可能在运行或等待;

等待:线程等待被其他线程唤醒,如调用了 wait、join 且没有指定超时时间;

限期等待:线程等待一段时间后被系统唤醒,如调用了 sleep、wait、join 并设置了超时时间;

阻塞:线程进入同步区域需要与其他线程协调同步,如需要进入 synchronized 区域但其他线程尚未退出此区域;

结束:run 方法执行完成后,线程结束。

虚拟机栈

在 Java 内存模型中,每个虚拟机线程都有自己私有的虚拟机栈。栈与线程同时创建,其中存储的是线程的栈帧 ( Stack Frame )。每个方法的调用,都对应着一个栈帧的入栈和出栈。在栈帧中,存储着局部变量表 ( Local Variable Table )、操作栈 ( Operand Stack )、动态连接 ( Dynamic Linking )、返回地址 ( Return Address ) 和其他附加信息。

线程的工作内存

在内存模型中,Java 要求所有的变量都必须存储在主内存中,每个线程拥有自己的工作内存。工作内存中保存了线程需要读写的变量的主内存的副本。线程对变量的读写操作都在工作内存中直接进行,并不会去操作主内存中的内容,主内存与工作内存的同步由虚拟机完成。不同线程不能访问彼此的工作内存,变量值的传递需要经过主内存才能完成。

Volatile 修饰的变量可以保证变量对所有线程可见,即某个线程修改变量后,其他线程总能立刻读到新值。即便如此,多线程并发时,对 volatile 变量进行自增自减操作也不能保证线程安全。

总结

线程在 Java 中只能通过创建 Thread 类的实例来创建。在 JDK 1.2 之后,Java 中的线程基于操作系统原生的线程模型来实现线程。线程的调度方式是抢占式调度,即由系统来负责各个线程的时间分配,并在线程使用完分配的时间后调度下一个线程。Java 定义了 5 种线程状态:新建、运行、等待、限期等待、阻塞和结束。

每个虚拟机线程都有自己私有的虚拟机栈。栈与线程同时创建,其中存储的是线程的栈帧。每个方法的调用,都对应着一个栈帧的入栈和出栈。每个线程拥有自己的工作内存,工作内存中保存了线程需要读写的变量的主内存的副本。线程对变量的读写操作都在工作内存中直接进行,并不会去操作主内存中的内容,主内存与工作内存的同步由虚拟机完成。

每周 3 篇学习笔记或技术总结,面向有一定基础的 Java 程序员,内容涉及 Java 进阶、虚拟机、MySQL、NoSQL、分布式计算、开源框架等多个领域。关注作者或微信公众号 backend-develop 第一时间获取最新内容。

原文地址:Java线程知识拾遗

最新文章

  1. linux-8 基本命令---date
  2. IOS Animation-KeyPath值
  3. JavaScript(复习总结)
  4. 深入浅出谈存储:如何区别NAS、SAN与DAS
  5. HTML5实现刮奖效果
  6. 把ResultSet对象转变成List对象
  7. vim高亮设置
  8. 从壹开始前后端分离 [.netCore 填坑 ] 三十四║Swagger:API多版本控制,带来的思考
  9. Win10+Ubuntu18.04双系统安装
  10. Java基础构造方法和this关键字整理
  11. Django进阶之中间件
  12. Freemarker导出word的简单使用
  13. 真实分享记录我学习Linux系统遇到的问题
  14. CMD命令行合并多个txt文件到一个txt文件
  15. [Java学习]集合
  16. OpenGL3D图形、旋转、纹理、键盘移动、光照、滤波、透明(完整) 转自http://www.cnblogs.com/tiandsp/archive/2012/01/23/2329049.html
  17. Linux基础命令---bzcat
  18. pandas.read_csv参数整理
  19. 20155224 2016-2017-2 《Java程序设计》第8周学习总结
  20. leetcode67

热门文章

  1. 百度地图四(Android百度地图Poi检索开发总结)
  2. Go-简介
  3. 机器学习可解释性系列 - 是什么&为什么&怎么做
  4. Camera光学、成像和 3A 算法 (视觉),camera开发
  5. MySQL的简单实用 手把手教学
  6. java进阶(26)--ForEach
  7. 原生tab选项卡
  8. Pycharm开发环境配置与调试
  9. 使用leveldb
  10. kali linux 换国内源