Java读源码之Thread
前言
JDK版本:1.8
阅读了Object的源码,wait和notify方法与线程联系紧密,而且多线程已经是必备知识,那保持习惯,就从多线程的源头Thread类开始读起吧。由于该类比较长,只读重要部分
源码
类声明和重要属性
package java.lang;
public class Thread implements Runnable {
private volatile String name;
// 优先级
private int priority;
//是否后台
private boolean daemon = false;
/* JVM state */
private boolean stillborn = false;
// 要跑的任务
private Runnable target;
// 线程组
private ThreadGroup group;
// 上下文加载器
private ClassLoader contextClassLoader;
// 权限控制上下文
private AccessControlContext inheritedAccessControlContext;
// 线程默认名字“Thread-{{ threadInitNumber }}”
private static int threadInitNumber;
// 线程本地局部变量,每个线程拥有各自独立的副本
ThreadLocal.ThreadLocalMap threadLocals = null;
// 有时候线程本地局部变量需要被子线程继承
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
// 线程初始化时申请的JVM栈大小
private long stackSize;
// 线程ID
private long tid;
// 线程init之后的ID
private static long threadSeqNumber;
// 0就是线程还处于NEW状态,没start
private volatile int threadStatus = 0;
// 给LockSupport.park用的需要竞争的对象
volatile Object parkBlocker;
// 给中断用的需要竞争的对象
private volatile Interruptible blocker;
// 线程最小优先级
public final static int MIN_PRIORITY = 1;
// 线程默认优先级
public final static int NORM_PRIORITY = 5;
// 线程最大优先级
public final static int MAX_PRIORITY = 10;
Java线程有几种状态?
// Thread类中的枚举
public enum State {
// 线程刚创建出来还没start
NEW,
// 线程在JVM中运行了,需要去竞争资源,例如CPU
RUNNABLE,
// 线程等待获取对象监视器锁,损被别人拿着就阻塞
BLOCKED,
// 线程进入等待池了,等待觉醒
WAITING,
// 指定了超时时间
TIMED_WAITING,
// 线程终止
TERMINATED;
}
下面这个图可以帮助理解Java线程的生命周期,这个图要会画!面试中被问到,当时画的很不专业,难受!
创建
那么线程如何进入初始New状态呢?让我们来看看构造,头皮发麻,怎么有七八个构造,这里只贴了一个
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
还好都是调用init()方法,怕怕的点开了
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 获取当前线程,也就是需要被创建线程的爸爸
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
// 通过security获取线程组,其实就是拿的当前线程的组
if (security != null) {
g = security.getThreadGroup();
}
// 获取当前线程的组,这下确保肯定有线程组了
if (g == null) {
g = parent.getThreadGroup();
}
}
// check一下组是否存在和是否有线程组修改权限
g.checkAccess();
// 子类执行权限检查,子类不能重写一些不是final的敏感方法
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
// 组里未启动的线程数加1,长时间不启动就会被回收
g.addUnstarted();
// 线程的组,是否后台,优先级,初始全和当前线程一样
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
// 子类重写check没过或者就没有security,这里要check下是不是连装载的权限都没有
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
// 访问控制上下文初始化
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
// 任务初始化
this.target = target;
// 设置权限
setPriority(priority);
// 如果有需要继承的ThreadLocal局部变量就copy一下
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// 初始化JVM中待创建线程的栈大小
this.stackSize = stackSize;
// threadSeqNumber线程号加1
tid = nextThreadID();
}
运行
现在线程已经是NEW状态了,我们还需要调用start方法,让线程进入RUNNABLE状态,真正在JVM中快乐的跑起来,当获得了执行任务所需要的资源后,JVM便会调用target(Runnable)的run方法。
注意:我们永远不要对同一个线程对象执行两次start方法
public synchronized void start() {
// 0就是NEW状态
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 把当前线程加到线程组的线程数组中,然后nthreads线程数加1,nUnstartedThreads没起的线程数减1
group.add(this);
boolean started = false;
// 请求资源
try {
start0();
started = true;
} finally {
try {
if (!started) {
// 起失败啦,把当前线程从线程组的线程数组中删除,然后nthreads减1,nUnstartedThreads加1
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
// start0出问题会自己打印堆栈信息
}
}
}
private native void start0();
终止
现在我们的线程已经到RUNNABLE状态了,一切顺利的话任务执行完成,自动进入TERMINATED状态,天有不测风云,我们还会再各个状态因为异常到达TERMINATED状态。
Thread类为我们提供了interrupt方法,可以设置中断标志位,设置了中断之后不一定有影响,还需要满足一定的条件才能发挥作用:
- RUNNABLE状态下
- 默认什么都不会发生,需要代码中循环检查 中断标志位
- WAITING/TIMED_WAITING状态下
- 这两个状态下,会从对象等待池中出来,等拿到监视器锁会抛出InterruptedException异常,然后中断标志位被清空。
- BLOCKED状态下
- 如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程依然会处于BLOCKED状态
- NEW/TERMINATE状态下
- 啥也不发生
// 设置别的线程中断
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
// 拿一个可中断对象Interruptible的锁
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 设置中断标志位
b.interrupt(this);
return;
}
}
interrupt0();
}
// 获取当前线程中断标志位,然后重置中断标志位
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
// 检查线程中断标志位
public boolean isInterrupted() {
return isInterrupted(false);
}
等待
主线已经做完了,下面来看下支线任务,同样重要哦。从线程状态图看到,RUNNABLE状态可以变成BLOCKED,WAITING或TIMED_WAITING。
其中BLOCKED主要是同步方法竞争锁等同步资源造成的,而TIMED_WAITING主要是加了超时时间,其他和WAITING的内容差不多,唯一多了一个sleep方法。
sleep
果不其然,sleep方法和Object.wait方法如出一辙,都是调用本地方法,提供毫秒和纳秒两种级别的控制,唯一区别就是,sleep不会放弃任何占用的监视器锁
public static native void sleep(long millis) throws InterruptedException;
// 纳秒级别控制
public static void sleep(long millis, int nanos) throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
join
join方法会让线程进入WAITING,等待另一个线程的终止,整个方法和Object.wait方法也是很像,而且实现中也用到了wait,既然用到了wait方法,自然也会释放调用对象的监视器锁
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
// 判断调用join的线程是否活着,这里的活着是指RUNNABLE,BLOCKED,WAITING,TIMED_WAITING这四种状态,如果活着就一直等着,wait(0)意味着无限等
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
// 纳秒级别控制
public final synchronized void join(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
public final void join() throws InterruptedException {
join(0);
}
其他方法
yield
告诉操作系统的调度器:我的cpu可以先让给其他线程,但是我占有的同步资源不让。
注意,调度器可以不理会这个信息。这个方法几乎没用,调试并发bug可能能派上用场
public static native void yield();
setPriority
有些场景是需要根据线程的优先级来调度的,优先级越大越先执行,最大10,默认5,最小1
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
// 如果设置的优先级,比线程所属线程组中优先级的最大值还大,我们需要更新最大值
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
// 本地方法
setPriority0(priority = newPriority);
}
}
弃用方法
有些熟悉的方法已经被弃用了,我们要避免使用
@Deprecated
public final void stop()
@Deprecated
public final synchronized void stop(Throwable obj)
@Deprecated
public void destroy()
@Deprecated
public final void suspend()
@Deprecated
public final void resume()
@Deprecated
public native int countStackFrames()
实践
interrupt()
public class ThreadInterruptTest {
/**
* 如果我们同时调用了notify和interrupt方法,程序有可能正常执行结束,有可能抛出异常结束,
* 原因是不管是因为notify还是interrupt,线程离开了等待池,都需要去竞争锁,
* 如果interrupt调用瞬间拿到锁,notify还没有调用,就抛中断异常
* 如果是interrupt调用瞬间拿不到锁,此时中断标志位被重置,然后notify把线程拉到正常轨道,就继续执行不抛中断异常
*/
private static void testInterrupt() {
Object object = new Object();
Thread thread1 = new Thread(() -> {
synchronized (object) {
try {
object.wait();
System.out.println("我还活着!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
});
thread1.start();
new Thread(() -> {
// 只为了演示,实际很少用到这些方法,而且我们在执行中断的同步代码块中最好不要做别的事情,例如这里的notify
synchronized (object) {
thread1.interrupt();
object.notify();
}
}).start();
}
public static void main(String[] args) {
for (int i = 0; i <5 ; i++) {
ThreadInterruptTest.testInterrupt();
}
}
}
/**
* 输出:
* 我还活着!
* java.lang.InterruptedException
* at java.lang.Object.wait(Native Method)
* at java.lang.Object.wait(Object.java:502)
* at study.ThreadInterruptTest.lambda$testInterrupt$0(ThreadInterruptTest.java:15)
* at java.lang.Thread.run(Thread.java:748)
* java.lang.InterruptedException
* at java.lang.Object.wait(Native Method)
* at java.lang.Object.wait(Object.java:502)
* at study.ThreadInterruptTest.lambda$testInterrupt$0(ThreadInterruptTest.java:15)
* at java.lang.Thread.run(Thread.java:748)
* 我还活着!
* java.lang.InterruptedException
* at java.lang.Object.wait(Native Method)
* at java.lang.Object.wait(Object.java:502)
* at study.ThreadInterruptTest.lambda$testInterrupt$0(ThreadInterruptTest.java:15)
* at java.lang.Thread.run(Thread.java:748)
*
*/
join()
public class ThreadJoinTest {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("你好");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("你更好!");
});
thread1.start();
new Thread(() -> {
System.out.println("你也好");
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("你最好!!");
}).start();
}
/**
* 输出:
* 你好
* 你也好
* 你更好!
* 你最好!!
*/
}
Java读源码之Thread的更多相关文章
- Java读源码之ReentrantLock
前言 ReentrantLock 可重入锁,应该是除了 synchronized 关键字外用的最多的线程同步手段了,虽然JVM维护者疯狂优化 synchronized 使其已经拥有了很好的性能.但 R ...
- Java读源码之ReentrantLock(2)
前言 本文是 ReentrantLock 源码的第二篇,第一篇主要介绍了公平锁非公平锁正常的加锁解锁流程,虽然表达能力有限不知道有没有讲清楚,本着不太监的原则,本文填补下第一篇中挖的坑. Java读源 ...
- Java读源码之CountDownLatch
前言 相信大家都挺熟悉 CountDownLatch 的,顾名思义就是一个栅栏,其主要作用是多线程环境下,让多个线程在栅栏门口等待,所有线程到齐后,栅栏打开程序继续执行. 案例 用一个最简单的案例引出 ...
- Java读源码之ThreadLocal
前言 JDK版本: 1.8 之前在看Thread源码时候看到这么一个属性 ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal实现的是 ...
- Java读源码之Object
前言 JDK版本: 1.8 最近想看看jdk源码提高下技术深度(比较闲),万物皆对象,虽然Object大多native方法但还是很重要的. 源码 package java.lang; /** * Ja ...
- Java读源码之LockSupport
前言 JDK版本: 1.8 作用 LockSupport类主要提供了park和unpark两个native方法,用于阻塞和唤醒线程.注释中有这么一段: 这个类是为拥有更高级别抽象的并发类服务的,开发中 ...
- java读源码 之 map源码分析(HashMap,图解)一
开篇之前,先说几句题外话,写博客也一年多了,一直没找到一种好的输出方式,博客质量其实也不高,很多时候都是赶着写出来的,最近也思考了很多,以后的博客也会更注重质量,同时也尽量写的不那么生硬,能让大家 ...
- java读源码 之 queue源码分析(PriorityQueue,附图)
今天要介绍的是基础容器类(为了与并发容器类区分开来而命名的名字)中的另一个成员--PriorityQueue,它的大名叫做优先级队列,想必即使没有用过也该有所耳闻吧,什么?没..没听过?emmm... ...
- java读源码 之 list源码分析(LinkedList)
文章目录 LinkedList: 继承关系分析: 字段分析: 构造函数分析: 方法分析: LinkedList: 继承关系分析: public class LinkedList<E> ex ...
随机推荐
- WebStorm调节控制台字体
File --> Settings --> Editor --> Colors & Fonts --> Console Font
- ubantu下配置共享文件
原文转自 http://blog.chinaunix.net/uid-25305993-id-3754109.html 一 samba的安装: sudo apt-get install samba ...
- spring boot监听器的实现
spring boot监听器的实现 如下所示: import javax.servlet.ServletContextEvent; import javax.servlet.ServletContex ...
- Navicate for mysql如何导入一个sql文件
我在做的项目是宜立方商城的项目,现在需要把见表的sql文件导入到navicate中去,步骤如下: ①新建一个数据库,如下: ②在数据库名字上右键,选择运行sql文件 ③选择如下sql文件 ④刷新之后:
- 新建项目中的fail和missing之类的问题
首先必须要明白:每一个项目需要都要安装在本地仓库中去--->pom,war,jar无一例外 如果不安装会出现下面的错误: 解决的办法就是: 先clean后install项目,把他们安装到本地仓库 ...
- 【LeetCode】Two Sum II - Input array is sorted
[Description] Given an array of integers that is already sorted in ascending order, find two numbers ...
- sql 多行、一行 互转
原始数据: 期望数据: IF OBJECT_ID('temp_20170701','u') IS NOT NULL DROP TABLE temp_20170701 CREATE TABLE temp ...
- 金融风控100道面试题:传统银行开发转行互金top3公司并年薪40多万
知乎上有一个50万人看过的问题“为什么自学Python看不进去?”,其实原因很简单,大家缺乏能动手实战的机会. 知识要是死记硬背记在在脑海中,用不了多久就会忘记,只用依靠实战才能让知识落地. 小七这次 ...
- react navigation goBack()返回到任意页面(不集成redux) 二
实现思路: A -- > B (获取A的key值,传至C)-- >C(获取B传来的A页面key值,传至D) -- >D(获取C传来的A页面key值&C页面的key值,传至下一 ...
- 即时聊天APP(六) - 消息的接收以及EventBus使用
通常我们在接收消息的时候会有声音和震动的提示,因此我也加了代码达到这样的效果,这就要用到EventBus了,当然这里我也用到了自定义的广播,所以首先在Mainfests文件中加入以下代码: <r ...