引言

相信各位道友在平时工作中已经很少直接用到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的演示如下:

 public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
System.out.println("thread获取到锁,触发wait");
obj.wait();
System.out.println("wait over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
System.out.println("thread1获取到锁");
Thread.sleep(1000);
System.out.println("1秒后唤醒");
obj.notify();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("notify over");
} }
});
thread.start();
thread1.start();
}

执行结果为:

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

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

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

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

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

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

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

 public class RunnableThread implements Runnable{
@Override
public void run() {
System.out.println("runnable run");
try {
System.out.println("开始睡眠");
Thread.sleep(5000);
System.out.println("睡了5秒");
} catch (Exception e) {
System.out.println("runnable exception:" + e);
}
} public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Thread thread = new Thread(new RunnableThread());
thread.start();
thread.join();
System.out.println("end");
}
}

执行结果为:

runnable run
开始睡眠
睡了5秒
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. 20.Yii2.0框架多表关联一对多查询之hasMany

    目录 新手模式 hasMany关联模式查询 新建mode层Article.php 新建mode层Category.php 新建控制器HomeController.php 新手模式 用上次的查询结果,作 ...

  2. python学习博客推荐

    https://www.liaoxuefeng.com/

  3. MongoDB之Replica Sets环境搭建

    最近学习MongoDB,这两天在搭建复制集的时候碰到了不少问题,也踩了好多坑,现在在这里记录下来,以供自己和他人参考 (因为本人是初学者,所以遇到的问题也会比较初级,所以本文也比较适合初学者查阅) 背 ...

  4. isinstance() 函数

    Python3 isinstance() 函数   描述 isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type(). isinstance() 与 type() 区别: t ...

  5. iOS启动图 LaunchImage LaunchScreen.xib

    1.Images.xcassets添加LaunchImage 2.具体大小和添加类别都是可以调的 640*960   (4/4s)                                 2X ...

  6. day01_06.比较运算符

    >  >=  =  <  <=  != == ===  !== 凡运算,必有运算结果,比较运算符的运算结果是布尔值 ==和===的区别 <?php $c = ( 3 == ...

  7. Oracle PL/SQL 语言(Procedural Language/SQL)

    Oracle PL/SQL 语言(Procedural Language/SQL)是结合了结构化查询与 Oracle 自身过程控制为一体的强大语言,PL/SQL 不但支持更多的数据类型,拥有自身的变量 ...

  8. POJ 2181 Jumping Cows

    Jumping Cows Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 6398   Accepted: 3828 Desc ...

  9. php rabbitmq操作类及生产者和消费者实例代码 转

    注意事项: 1.accept.php消费者代码需要在命令行执行 2.'username'=>'asdf','password'=>'123456' 改成自己的帐号和密码 RabbitMQC ...

  10. 【bzoj3105】[cqoi2013]新Nim游戏 高斯消元求线性基

    题目描述 传统的Nim游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同).两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴.可以只拿一根,也可以拿走整堆火柴,但不能同时从 ...