引言

相信各位道友在平时工作中已经很少直接用到Thread线程类了,现在大多是通过线程池或者一些多线程框架来操作线程任务,但我觉得还是有必要了解清楚Thread线程类中各种方法的含义,了解了底层才能更好的理解框架、应用框架。下面我就将Thread线程的相关基础点总结一二,以供观瞻。

正文

1、Thread线程的状态

根据《深入理解Java虚拟机》一书的讲述,Java语言定义了五种线程状态,分别为:创建(new)、运行(Runnable)、等待(waiting)、阻塞(blocked)、结束(terminated)。而且规定,在某一个时间点,每个线程能且只能处于其中的一种状态。

其中,运行状态又包括就绪(Ready)跟正在运行(Running),区别就是是否获得了CPU的执行时间。

对于等待跟阻塞状态,需要着重说明一下,因为此处极易搞错,而且也是面试常被问到的点。等待状态,一般由Object.wait()、Thread.sleep()、Thread.join()、LockSupport.park()等方法以及这些方法带时间控制的同类方法实现线程的等待。而阻塞状态,一般是由于当前线程还未获取到独占锁且正在等待获取,此时称为阻塞。可以将等待看做主动的线程暂停执行,以为需要调用特定的方法线程才会等待;而阻塞可以看做是被动的线程暂定执行,因为线程在等着获取独占锁。

2、Thread线程的相关方法

start()方法/run()方法:有时在面试的时候,面试官会问到调用线程的start方法跟直接调用run方法有什么区别?虽然有的道友看到这里会觉得问这种问题的面试官有点很没必要,但我还是说一下。调用start方法后,最终会调用Thread类中的一个本地方法start0,这个方法可以新建一个线程来运行你的run方法,而调用run方法后只是在当前线程上运行你的run方法,并没有新线程参与。

wait()方法/sleep()方法:请注意,这里很多人都会记错,wait方法以及跟它配套的notify/notifyAll方法,是位于顶级父类Object下的,而其他操作线程的方法都在Thread线程类下。为什么要将wait方法放在Object下呢?其实这是由wait/notify方法的实现原理决定的。wait方法调用了之后,会释放锁,并让当前线程等待,而对于java的原生锁synchronized,是隶属于一个特定对象的监视器monitor的,那这个释放的是锁谁的锁?不能是别人的,只能是调用wait方法的那个对象的。而这个锁是哪里来的?要释放锁,肯定之前加过锁,在哪里加的呢?只能是在synchronized块中给这个对象加的,所以这也解释了为什么wait/notify方法一直要跟synchronized一起用,因为它俩就是通过操作对象的锁实现的等待和唤醒。相比而言sleep方法单纯很多,它只是让当前线程睡眠一段时间,并不会涉及到对锁的操作,所以直接放在Thread类中就行。对于wait跟notify的演示如下:

  1. public static void main(String[] args) throws InterruptedException {
  2. Object obj = new Object();
  3. Thread thread = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. synchronized (obj) {
  7. try {
  8. System.out.println("thread获取到锁,触发wait");
  9. obj.wait();
  10. System.out.println("wait over");
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. });
  17. Thread thread1 = new Thread(new Runnable() {
  18. @Override
  19. public void run() {
  20. synchronized (obj) {
  21. try {
  22. System.out.println("thread1获取到锁");
  23. Thread.sleep(1000);
  24. System.out.println("1秒后唤醒");
  25. obj.notify();
  26. } catch (Exception e) {
  27. e.printStackTrace();
  28. }
  29. System.out.println("notify over");
  30. }
  31.  
  32. }
  33. });
  34. thread.start();
  35. thread1.start();
  36. }

执行结果为:

  1. thread获取到锁,触发wait
  2. thread1获取到锁
  3. 1秒后唤醒
  4. notify over
  5. wait over

LockSupport.park():另外还有JUC包中的park方法让当前线程等待。此方法是使用CAS实现的线程等待,不会释放锁。而park/unpark方法比wait/notify这一对好的地方在于,前者可以先unpark在park,这是线程仍然会继续执行;而对于wait/notify,则需要通过程序控制执行顺序,一定要先wait在notify/notifyAll,否则顺序反了线程就会一直等待下去,由此悲剧诞生...  比如讲上述wait/notify的代码34行35行调换一下顺序,执行结果如下所示:

  1. thread1获取到锁
  2. 1秒后唤醒
  3. notify over
  4. thread获取到锁,触发wait

仿佛云天明对程心那一千八百万年的等待

join()/yield():对于Thread下的这两个方法,之所以放在一起讲解,就是因为这两个方法平时比较少用到,属于闲云野鹤的存在。

yield()方法是让当前线程让步,让步的意思就是放弃执行权,即当前线程会从上述说的运行状态runnable中的running状态进入ready就绪状态,但是虚拟机不保证当前线程执行了yield方法后不会紧接着再次进去running状态,因为可能CPU分配执行时间时又分给了当前线程。所以这个方法其实一般也没啥用,因为效果不稳定。

join()方法是将调用join的线程插入当前线程的执行过程中,即让当前线程等待,先执行完调用join的线程,再继续执行当前线程。注意join方法不会释放锁。join的演示代码如下:

  1. public class RunnableThread implements Runnable{
  2. @Override
  3. public void run() {
  4. System.out.println("runnable run");
  5. try {
  6. System.out.println("开始睡眠");
  7. Thread.sleep(5000);
  8. System.out.println("睡了5秒");
  9. } catch (Exception e) {
  10. System.out.println("runnable exception:" + e);
  11. }
  12. }
  13.  
  14. public static void main(String[] args) throws InterruptedException {
  15. Object obj = new Object();
  16. Thread thread = new Thread(new RunnableThread());
  17. thread.start();
  18. thread.join();
  19. System.out.println("end");
  20. }
  21. }

执行结果为:

  1. runnable run
  2. 开始睡眠
  3. 睡了5
  4. end

结束语

这次先到这里,上述说的东西,虽然很小,而且实际中不会直接用到,但是对于我们理解线程的运行机制、理解多线程框架都有好处,所以还是有必要在自己的学习地图上理解清楚。其实线程还有一个很重要的点就是线程的中断,多线程框架或者JUC包的源码中都会涉及到对线程中断的处理以及响应,这一块我会在后面梳理清楚了之后专门整理出来。最近觉得学习进入了停滞期,有点不知道从何下手,觉得需要学的东西太多。在这里,想跟各位道友讨教一下,一个资质普通的开发者,如何才能将自己的实力提升到一个比较高的层次(比如阿里的P6P7及以上?)欢迎留言赐教,在此不胜感激!

Thread线程的基础知识及常见疑惑点的更多相关文章

  1. java核心-多线程(4)-线程类基础知识

    1.并发 <1>使用并发的一个重要原因是提高执行效率.由于I/O等情况阻塞,单个任务并不能充分利用CPU时间.所以在单处理器的机器上也应该使用并发. <2>为了实现并发,操作系 ...

  2. Java线程并发控制基础知识

    微博上众神推荐今年4月刚刚出版的一本书,淘宝华黎撰写的<大型网站系统与Java中间件实践>,一线工程师的作品,实践出真知,果断要看. 前两章与<淘宝技术这十年>内容类似,基本是 ...

  3. Boost::Thread 多线程的基础知识

    Boost.Thread可以使用多线程执行可移植C++代码中的共享数据.它提供了一些类和函数来管理线程本身,还有其它一些为了实现在线程之间同步数据或者提供针对特定单个线程的数据拷贝.头文件:#incl ...

  4. HTML基础知识(常见元素、列表、链接元素、图片元素)

    1.HTML有关概念 全称: Hyper Text Markup Language(超文本标记语言) 其文件扩展名为".html"或".htm" * 超文本 - ...

  5. python基础知识8——常见内置模块

    Python之路-python(常用模块学习) 模块介绍 time &datetime模块 random os sys shutil shelve xml处理 yaml处理 configpar ...

  6. Java基础知识之常见关键字(1)

    static 特点: 随着类的加载而加载 优先于对象存在 被所有对象所共享 可以直接被类名调用 注意点: 静态方法只能访问静态方法 但是非静态成员可以直接访问静态成员 静态方法中不可以使用this , ...

  7. Java基础知识之常见关键字以及概念总结

    static 特点: 随着类的加载而加载 优先于对象存在 被所有对象所共享 可以直接被类名调用 使用注意: 静态方法只能访问静态成员 但是非静态成员可以直接访问静态成员 静态方法中不可用使用this, ...

  8. 大数据学习之路——MySQL基础(一)——MySQL的基础知识与常见操作

    一.存储引擎 1.含义 存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建.查询.更新和删除数据.不同的存储引擎提供不同的存储机制.索引技巧.锁定水平等功能,使用不同的存储引 ...

  9. java编程基础知识及常见例题

    ⒈标识符: 只能包含数字.字母.下划线.$,并且不能以数字开头.语义直观规范 驼峰法则: 如:方法名.变量名采用驼峰法则 帕斯卡命名法: 如: 类.接口.枚举采用帕斯卡命名法包名:网址倒写,com.网 ...

随机推荐

  1. 阿里大鱼短信发送 FOR DT

    //增加了参数$action 来标志发送的是什么短信 注册短信 验证码短信 提示短信等 function send_sms($mobile, $message, $word = 0, $time = ...

  2. css3属性:美化表单、点击元素产生的背景与边框怎么去掉,滚动回弹效果

  3. python数据类型之字典(dict)和其常用方法

    字典的特征: key-value结构key必须可hash,且必须为不可变数据类型.必须唯一. # hash值都是数字,可以用类似于2分法(但比2分法厉害的多的方法)找.可存放任意多个值.可修改.可以不 ...

  4. FSMC原理通俗解释

    所以不用GPIO口直接驱动液晶,是因为这种方法速度太慢,而FSMC是用来外接各种存储芯片的,所以其数据通信速度是比普通GPIO口要快得多的.TFT-LCD 驱动芯片的读写时序和SRAM的差不多,所以就 ...

  5. STM32——PWM基本知识及配置过程

    将通用定时器分为四个部分: 1,选择时钟 2,时基电路 3,输入捕获 4,输出比较 本节定时器PWM输出主要涉及到定时器框图右下方部分,即输出比较部分 和上一讲相同,时基时钟来源于内部默认时钟 对此有 ...

  6. poj-1979 red and black(搜索)

    Time limit1000 ms Memory limit30000 kB There is a rectangular room, covered with square tiles. Each ...

  7. ACM-ICPC 2018 徐州赛区网络预赛 H. Ryuji doesn't want to study

    262144K   Ryuji is not a good student, and he doesn't want to study. But there are n books he should ...

  8. C#中的扩展方法详解

    “扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型.重新编译或以其他方式修改原始类型.”这是msdn上说的,也就是你可以对String,Int,DataRow,DataTable等这些类 ...

  9. webdriver高级应用- 禁止Chrome浏览器的PDF和Flash插件

    #encoding=utf-8 from selenium import webdriver # 导入Options类 from selenium.webdriver.chrome.options i ...

  10. Leetcode 417.太平洋大西洋水流问题

    太平洋大西洋水流问题 给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度."太平洋"处于大陆的左边界和上边界,而"大西洋"处于大陆的右边界和下 ...