Thread源码剖析
前言
昨天已经写了:
如果没看的同学建议先去阅读一遍哦~
在写文章之前通读了一遍《Java 核心技术 卷一》的并发章节和《Java并发编程实战》前面的部分,回顾了一下以前写过的笔记。从今天开始进入多线程的知识点咯~
我其实也是相当于从零开始学多线程的,如果文章有错的地方还请大家多多包含,不吝在评论区下指正呢~~
一、Thread线程类API
声明本文使用的是JDK1.8
实现多线程从本质上都是由Thread类来进行操作的~我们来看看Thread类一些重要的知识点。Thread这个类很大,不可能整个把它看下来,只能看一些常见的、重要的方法。
顶部注释的我们已经解析过了,如果不知道的同学可前往:多线程三分钟就可以入个门了!
1.1设置线程名
我们在使用多线程的时候,想要查看线程名是很简单的,调用Thread.currentThread().getName()
即可。
如果没有做什么的设置,我们会发现线程的名字是这样子的:主线程叫做main,其他线程是Thread-x
下面我就带着大家来看看它是怎么命名的:
nextThreadNum()
的方法实现是这样的:
基于这么一个变量-->线程初始化的数量
点进去看到init方法就可以确定了:
看到这里,如果我们想要为线程起个名字,那也是很简单的。Thread给我们提供了构造方法!
下面我们来测试一下:
- 实现了Runnable的方式来实现多线程:
public class MyThread implements Runnable { @Override public void run() { // 打印出当前线程的名字 System.out.println(Thread.currentThread().getName()); } }
测试:
public class MyThreadDemo { public static void main(String[] args) { MyThread myThread = new MyThread(); //带参构造方法给线程起名字 Thread thread1 = new Thread(myThread, "关注公众号Java3y"); Thread thread2 = new Thread(myThread, "qq群:742919422"); thread1.start(); thread2.start(); // 打印当前线程的名字 System.out.println(Thread.currentThread().getName()); } }
结果:
当然了,我们还可以通过setName(String name)
的方法来改掉线程的名字的。我们来看看方法实现;
检查是否有权限修改:
至于threadStatus这个状态属性,貌似没发现他会在哪里修改:
1.2守护线程
守护线程是为其他线程服务的
- 垃圾回收线程就是守护线程~
守护线程有一个特点:
- 当别的用户线程执行完了,虚拟机就会退出,守护线程也就会被停止掉了。
- 也就是说:守护线程作为一个服务线程,没有服务对象就没有必要继续运行了
使用线程的时候要注意的地方
- 在线程启动前设置为守护线程,方法是
setDaemon(boolean on)
- 使用守护线程不要访问共享资源(数据库、文件等),因为它可能会在任何时候就挂掉了。
- 守护线程中产生的新线程也是守护线程
测试一波:
public class MyThreadDemo { public static void main(String[] args) { MyThread myThread = new MyThread(); //带参构造方法给线程起名字 Thread thread1 = new Thread(myThread, "关注公众号Java3y"); Thread thread2 = new Thread(myThread, "qq群:742919422"); // 设置为守护线程 thread2.setDaemon(true); thread1.start(); thread2.start(); System.out.println(Thread.currentThread().getName()); } }
上面的代码运行多次可以出现(电脑性能足够好的同学可能测试不出来):线程1和主线程执行完了,我们的守护线程就不执行了~
原理:这也就为什么我们要在启动之前设置守护线程了。
1.3优先级线程
线程优先级高仅仅表示线程获取的CPU时间片的几率高,但这不是一个确定的因素!
线程的优先级是高度依赖于操作系统的,Windows和Linux就有所区别(Linux下优先级可能就被忽略了)~
可以看到的是,Java提供的优先级默认是5,最低是1,最高是10:
实现:
setPriority0
是一个本地(navite)的方法:
private native void setPriority0(int newPriority);
1.4线程生命周期
在上一篇介绍的时候其实也提过了线程的线程有3个基本状态:执行、就绪、阻塞
在Java中我们就有了这个图,Thread上很多的方法都是用来切换线程的状态的,这一部分是重点!
其实上面这个图是不够完整的,省略掉了一些东西。后面在讲解的线程状态的时候我会重新画一个~
下面就来讲解与线程生命周期相关的方法~
1.4.1sleep方法
调用sleep方法会进入计时等待状态,等时间到了,进入的是就绪状态而并非是运行状态!
于是乎,我们的图就可以补充成这样:
1.4.2yield方法
调用yield方法会先让别的线程执行,但是不确保真正让出
- 意思是:我有空,可以的话,让你们先执行
于是乎,我们的图就可以补充成这样:
1.4.3join方法
调用join方法,会等待该线程执行完毕后才执行别的线程~
我们进去看看具体的实现:
wait方法是在Object上定义的,它是native本地方法,所以就看不了了:
wait方法实际上它也是计时等待(如果带时间参数)的一种!,于是我们可以补充我们的图:
1.4.3interrupt方法
线程中断在之前的版本有stop方法,但是被设置过时了。现在已经没有强制线程终止的方法了!
由于stop方法可以让一个线程A终止掉另一个线程B
- 被终止的线程B会立即释放锁,这可能会让对象处于不一致的状态。
- 线程A也不知道线程B什么时候能够被终止掉,万一线程B还处理运行计算阶段,线程A调用stop方法将线程B终止,那就很无辜了~
总而言之,Stop方法太暴力了,不安全,所以被设置过时了。
我们一般使用的是interrupt来请求终止线程~
- 要注意的是:interrupt不会真正停止一个线程,它仅仅是给这个线程发了一个信号告诉它,它应该要结束了(明白这一点非常重要!)
- 也就是说:Java设计者实际上是想线程自己来终止,通过上面的信号,就可以判断处理什么业务了。
- 具体到底中断还是继续运行,应该由被通知的线程自己处理
Thread t1 = new Thread( new Runnable(){ public void run(){ // 若未发生中断,就正常执行任务 while(!Thread.currentThread.isInterrupted()){ // 正常任务代码…… } // 中断的处理代码…… doSomething(); } } ).start();
再次说明:调用interrupt()并不是要真正终止掉当前线程,仅仅是设置了一个中断标志。这个中断标志可以给我们用来判断什么时候该干什么活!什么时候中断由我们自己来决定,这样就可以安全地终止线程了!
我们来看看源码是怎么讲的吧:
再来看看刚才说抛出的异常是什么东东吧:
所以说:interrupt方法压根是不会对线程的状态造成影响的,它仅仅设置一个标志位罢了
interrupt线程中断还有另外两个方法(检查该线程是否被中断):
- 静态方法interrupted()-->会清除中断标志位
- 实例方法isInterrupted()-->不会清除中断标志位
上面还提到了,如果阻塞线程调用了interrupt()方法,那么会抛出异常,设置标志位为false,同时该线程会退出阻塞的。我们来测试一波:
public class Main { /** * @param args */ public static void main(String[] args) { Main main = new Main(); // 创建线程并启动 Thread t = new Thread(main.runnable); System.out.println("This is main "); t.start(); try { // 在 main线程睡个3秒钟 Thread.sleep(3000); } catch (InterruptedException e) { System.out.println("In main"); e.printStackTrace(); } // 设置中断 t.interrupt(); } Runnable runnable = () -> { int i = 0; try { while (i < 1000) { // 睡个半秒钟我们再执行 Thread.sleep(500); System.out.println(i++); } } catch (InterruptedException e) { // 判断该阻塞线程是否还在 System.out.println(Thread.currentThread().isAlive()); // 判断该线程的中断标志位状态 System.out.println(Thread.currentThread().isInterrupted()); System.out.println("In Runnable"); e.printStackTrace(); } }; }
结果:
接下来我们分析它的执行流程是怎么样的:
2018年4月18日20:32:15(哇,这个方法真的消耗了我非常长的时间).....感谢@开始de痕迹的指教~~~~~
该参考资料:
- https://www.cnblogs.com/w-wfy/p/6414801.html
- https://www.cnblogs.com/carmanloneliness/p/3516405.html
- https://www.zhihu.com/question/41048032/answer/89478427
- https://www.zhihu.com/question/41048032/answer/89431513
二、总结
可以发现我们的图是还没有补全的~后续的文章讲到同步的时候会继续使用上面的图的。在Thread中重要的还是那几个可以切换线程状态的方法,还有理解中断的真正含义。
使用线程会导致我们数据不安全,甚至程序无法运行的情况的,这些问题都会再后面讲解到的~
之前在学习操作系统的时候根据《计算机操作系统-汤小丹》这本书也做了一点点笔记,都是比较浅显的知识点。或许对大家有帮助
参考资料:
- 《Java核心技术卷一》
- 《Java并发编程实战》
- 《计算机操作系统-汤小丹》
如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y。为了大家方便,刚新建了一下qq群:742919422,大家也可以去交流交流。谢谢支持了!希望能多介绍给其他有需要的朋友
文章的目录导航:
更多的文章可往: 文章的目录导航低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
JAVA IO流 字节流 字符流 缓冲流的注意点和效率
总结: stringBuilder的tostring返回的是new出来的字符串 如果没有那么返回空字符串而不是null 字符流:缓冲流内部都维护了一个数组 不是缓冲流的只有字符流的writer维护了一个数组 缓冲流中 只有字符流扩展了方法 reader扩展了readLine write扩展了newLine 但是字节流的话 缓冲流没有扩展方法 字符流读数据都是一个一个读的 而缓冲流内部维护了一个数组 也是一个一个读的 但是 字符流是从硬盘读 缓冲流是从内存读 快 字符流写数据都是写到内存中的数组 然后flush或者满了才写入硬盘 字符流的输入喜欢用缓冲 因为扩展了fileReader的功能 内部还缓冲了一个char数组 比如 readLine 一次读一行 返回的是string 到末尾返回null 字节流 :读入的话 缓冲流就是内部维护了一个数组 然后从里面读入 不是缓冲的话 自定义一个数组 然后读入会比较快 写入的话 如果是read 缓冲流快 但是如果自定义一个数组传入read的话 那效率还是 字节流快 因为 缓冲流就是帮我们省略了创建缓冲数组的步骤而已 缓冲流 不管是输出还是输入 都是...
- 下一篇
Java容器类详解
Java的容器 在Java中,我们想要保存对象可以使用很多种手段。最简单的就是数组。但是数组具有固定的尺寸,而通常来说,程序总是在运行时根据条件来创建对象,我们无法预知将要创建对象的个数以及类型,所以Java推出了容器类来解决这一问题。 Java的容器类分为List,Set,Queue和Map。 Java容器的基本概念 Java容器类库是用来保存对象的,他有两种不同的概念: Collection,独立元素的序列,这些元素都服从一条或多条规则。List、Set以及Queue都是Collection的一种,List必须按照顺序保存元素,而Set不能有重复元素,Queue需要按照排队规则来确定对象的顺序。 Map,Map是键值对类型,允许用户通过键来查找对象。Hash表允许我们使用另一个对象来查找某个对象。 Collection接口 尽管存在这两种概念,我们在工程中,大部分代码还是和接口打交道。Collection是最基本的集合接口。一个Collection代表一组Object,即Collection的元素(Elements)。Java SDK不提供直接继承自Collection的类,Ja...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Hadoop3单机部署,实现最简伪集群
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7安装Docker,走上虚拟化容器引擎之路