java thread源码分析_Thread源码剖析

前言

昨天已经写了:

如果没看的同学建议先去阅读一遍哦~

在写文章之前通读了一遍《Java 核心技术 卷一》的并发章节和《Java并发编程实战》前面的部分,回顾了一下以前写过的笔记。从今天开始进入多线程的知识点咯~

我其实也是相当于从零开始学多线程的,如果文章有错的地方还请大家多多包含,不吝在评论区下指正呢~~

一、Thread线程类API

声明本文使用的是JDK1.8

实现多线程从本质上都是由Thread类来进行操作的~我们来看看Thread类一些重要的知识点。Thread这个类很大,不可能整个把它看下来,只能看一些常见的、重要的方法。

顶部注释的我们已经解析过了,如果不知道的同学可前往:多线程三分钟就可以入个门了!

1.1设置线程名

我们在使用多线程的时候,想要查看线程名是很简单的,调用Thread.currentThread().getName()即可。

如果没有做什么的设置,我们会发现线程的名字是这样子的:主线程叫做main,其他线程是Thread-x

下面我就带着大家来看看它是怎么命名的:

7208ded38105d51f90fa2f919528f1ab.png

nextThreadNum()的方法实现是这样的:

b5c639366afc154725c60c089330ace3.png

基于这么一个变量-->线程初始化的数量

bdcc6be4c627c20bee5f731140c6e7f4.png

点进去看到init方法就可以确定了:

cbd12e1ec1ee71209f719a459c1ec8ca.png

看到这里,如果我们想要为线程起个名字,那也是很简单的。Thread给我们提供了构造方法!

2fe26be9edf0c44a87eee30333297cab.png

下面我们来测试一下:

实现了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());

}

}

结果:

d880f1a36946988c9b378bc18b6931af.png

当然了,我们还可以通过setName(String name)的方法来改掉线程的名字的。我们来看看方法实现;

b7c32a0a4ab6112f1366284ae769c6c5.png

检查是否有权限修改:

4433e3c6e07c083ff8c60134ddde5741.png

至于threadStatus这个状态属性,貌似没发现他会在哪里修改:

ee4033c47bfca2bec2d41ff60cb4de93.png

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和主线程执行完了,我们的守护线程就不执行了~

8f19ea1d932c797a7233fa1f23175a04.png

原理:这也就为什么我们要在启动之前设置守护线程了。

3fc48b01827ae203e1078e74815372e1.png

1.3优先级线程

线程优先级高仅仅表示线程获取的CPU时间片的几率高,但这不是一个确定的因素!

线程的优先级是高度依赖于操作系统的,Windows和Linux就有所区别(Linux下优先级可能就被忽略了)~

可以看到的是,Java提供的优先级默认是5,最低是1,最高是10:

cfee6cc140aff14876e49e8226be3da6.png

实现:

7ca551bf6a917f23efb53f91727b8f94.png

setPriority0是一个本地(navite)的方法:

private native void setPriority0(int newPriority);

1.4线程生命周期

在上一篇介绍的时候其实也提过了线程的线程有3个基本状态:执行、就绪、阻塞

在Java中我们就有了这个图,Thread上很多的方法都是用来切换线程的状态的,这一部分是重点!

63aa6b94cbbece11d2de993c9278b26d.png

其实上面这个图是不够完整的,省略掉了一些东西。后面在讲解的线程状态的时候我会重新画一个~

下面就来讲解与线程生命周期相关的方法~

1.4.1sleep方法

调用sleep方法会进入计时等待状态,等时间到了,进入的是就绪状态而并非是运行状态!

d439238ba30e4376f48ef13060e2783f.png

于是乎,我们的图就可以补充成这样:

f40b1217f5f71877f353d10d33bde366.png

1.4.2yield方法

调用yield方法会先让别的线程执行,但是不确保真正让出

意思是:我有空,可以的话,让你们先执行

0f18aff0db883fba47f631ac3e3fc718.png

于是乎,我们的图就可以补充成这样:

132ab89f43837bf49b44816e3b503341.png

1.4.3join方法

调用join方法,会等待该线程执行完毕后才执行别的线程~

357d97879d866d4a2574763a971d3611.png

我们进去看看具体的实现:

1a80f65fd2877147c945c39337255aa8.png

wait方法是在Object上定义的,它是native本地方法,所以就看不了了:

ac502c149b4df04211d2b21fddfef96a.png

wait方法实际上它也是计时等待(如果带时间参数)的一种!,于是我们可以补充我们的图:

4ef5aa3e1990f2b479d5106f5a38f51e.png

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()并不是要真正终止掉当前线程,仅仅是设置了一个中断标志。这个中断标志可以给我们用来判断什么时候该干什么活!什么时候中断由我们自己来决定,这样就可以安全地终止线程了!

我们来看看源码是怎么讲的吧:

232578f1d9b4740f3a2c62945bdbdc88.png

再来看看刚才说抛出的异常是什么东东吧:

6c072768b48741728ffbb3d1c157b7b3.png

所以说:interrupt方法压根是不会对线程的状态造成影响的,它仅仅设置一个标志位罢了

interrupt线程中断还有另外两个方法(检查该线程是否被中断):

静态方法interrupted()-->会清除中断标志位

实例方法isInterrupted()-->不会清除中断标志位

8fdd54eb46d30f85dda2d0a4e616c06c.png

6d0314a02bc16ba9432a9ce09a3f62f8.png

上面还提到了,如果阻塞线程调用了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();

}

};

}

结果:

9da3c4cb9d6720bedd894c33fd321487.png

接下来我们分析它的执行流程是怎么样的:

65f8552d149da8719609835276222b39.png

2018年4月18日20:32:15(哇,这个方法真的消耗了我非常长的时间).....感谢@开始de痕迹的指教~

该参考资料:

二、总结

可以发现我们的图是还没有补全的~后续的文章讲到同步的时候会继续使用上面的图的。在Thread中重要的还是那几个可以切换线程状态的方法,还有理解中断的真正含义。

使用线程会导致我们数据不安全,甚至程序无法运行的情况的,这些问题都会再后面讲解到的~

之前在学习操作系统的时候根据《计算机操作系统-汤小丹》这本书也做了一点点笔记,都是比较浅显的知识点。或许对大家有帮助

参考资料:

《Java核心技术卷一》

《Java并发编程实战》

《计算机操作系统-汤小丹》

如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y。为了大家方便,刚新建了一下qq群:742919422,大家也可以去交流交流。谢谢支持了!希望能多介绍给其他有需要的朋友

文章的目录导航:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的线程是通过Thread类来实现的,Thread类封装了所有线程相关的方法和属性。下面是Thread类的部分源码: ```java public class Thread implements Runnable { //线程状态 private volatile int threadStatus = 0; private static final int RUNNING = 1; private static final int SHUTDOWN = -1; private static final int STOP = -2; private static final int TIDYING = 2; private static final int TERMINATED = 3; //线程优先级 public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10; //线程组 private ThreadGroup group; private Runnable target; private String name; private long stackSize; private long eetop; //线程ID private long tid; //线程本地存储 ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; //线程中断标志 private volatile boolean interrupted = false; private static final int HIBERNATE = 0; private static final int WAITING = 1; private static final int TIMED_WAITING = 2; private static final int BLOCKED = 3; private static final int NEW = 0; //线程锁 private Object parkBlocker; //线程中断处理 private void handleInterrupt() { if (this != Thread.currentThread()) throw new RuntimeException("Only the original thread can be interrupted"); if (this.interrupted) { park(); } } //线程休眠 public static void sleep(long millis) throws InterruptedException { Thread.sleep(millis, 0); } //中断线程 public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (this) { interrupted = true; notifyAll(); } } //线程运行方法 @Override public void run() { if (target != null) { target.run(); } } } ``` 在Thread类中,我们可以看到一些重要的属性和方法,比如: - threadStatus: 线程状态,用整数表示,包含RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED五种状态。 - group: 线程所属的线程组。 - target: 线程要执行的任务。 - name: 线程名称。 - stackSize: 线程堆栈大小。 - tid: 线程ID。 - threadLocals: 线程本地存储。 - interrupted: 线程中断标志。 - parkBlocker: 线程锁。 - sleep(): 线程休眠方法。 - interrupt(): 中断线程方法。 - run(): 线程运行方法。 通过这些属性和方法,我们可以使用Java中的线程实现多线程编程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值