对于线程Thread类的使用,可以说是java语言必备,但你是否真正意义上去剖析过他的内部结构,本文从概述的几个问题出发,一起进行源码阅读(本文基于Android-27中的Thread源码)
概述
对常用的Thread做一次源码剖析,更好的去理解和使用它,看完之后你会明白的几个问题:
- 调用start发生了什么?多次调用start会怎么样?
- start和run方法的区别
- join和sleep的区别
- 什么是守护进程
一、创建使用
1. 初始化
Thread构造函数
内部调用--->init()方法
java.lang.Thread#Thread()
java.lang.Thread#Thread(java.lang.Runnable)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable)
java.lang.Thread#Thread(java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.String, int, boolean)
java.lang.Thread#Thread(java.lang.Runnable, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)
init()方法指定四个参数:ThreadGroup,任务runable,线程名称,栈大小,其中部分参数初始值都是继承父线程的属性
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
Thread parent = currentThread();//获取创建thread的线程
if (g == null) {
g = parent.getThreadGroup();
}
g.addUnstarted();//在ThreadGroup中标记增加了一个未启动的线程,里面操作很简单,nUnstartedThreads++;
this.group = g;
this.target = target;
this.priority = parent.getPriority();//继承父线程的等级
this.daemon = parent.isDaemon();//继承父线程的属性:是否为守护进程
setName(name);
init2(parent);//保存一些常量参数,如上,给子线程调用
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
tid = nextThreadID();
}
...
//线程 tid递增一个
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
2. start方法
-
在Android中,检测到再次调用start线程会抛出IllegalThreadStateException
public synchronized void start() { // Android-changed: throw if 'started' is true if (threadStatus != 0 || started) throw new IllegalThreadStateException(); //还记得上面init方法中,调用addUnstarted时,标记增加了未启动线程 //这里调用add方法,将线程添加到系统线程数组,并且将未启动线程数减一,相当于移出 group.add(this); started = false; try { nativeCreate(this, stackSize, daemon); //调用native方法启动线程,如果报错,则直接跳到finally执行,started为false, //启动失败,从group中移除,同时group中未启动线程数++ started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } }
3. run方法
//Thread实现的Runnable接口
class Thread implements Runnable {
...
//调用传入的Runnable的run方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
二、Thread阻塞
1.join方法
join方法用于等待线程执行完成,传入的时间单位为等待的最大时长,里面是一个 while (isAlive())循环函数,当不传入时间参数,则为永久等待直到线程结束,传入时间参数,当时间到达时会结束join方法
public final void join(long millis) throws InterruptedException {
synchronized(lock) {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
lock.wait(0);
}
} else {
//循环,当达到最大等待时常,则跳出循环
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
lock.wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
}
public final void join() throws InterruptedException {
join(0);
}
//等待多少毫秒在加多少纳秒
public final void join(long millis, int nanos)
throws InterruptedException {
synchronized(lock) {
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);
}
}
2.sleep方法
sleep作用是使当前线程睡眠指定时间,其中几个关键点
获取当前调用线程的lock:currentThread().lock;
-
通过while (true)循环sleep当前线程,并检测睡眠时间达到传输参数时间,break当前循环
public static void sleep(long millis, int nanos)throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("millis < 0: " + millis); } if (nanos < 0) { throw new IllegalArgumentException("nanos < 0: " + nanos); } if (nanos > 999999) { throw new IllegalArgumentException("nanos > 999999: " + nanos); } //当睡眠时间为0,先检测线程是否已经中断,是的话抛出异常,否则直接return if (millis == 0 && nanos == 0) { // ...but we still have to handle being interrupted. if (Thread.interrupted()) { throw new InterruptedException(); } return; } long start = System.nanoTime(); long duration = (millis * NANOS_PER_MILLI) + nanos; 获取当前线程的lock Object lock = currentThread().lock; // Wait may return early, so loop until sleep duration passes. synchronized (lock) { while (true) { sleep(lock, millis, nanos); long now = System.nanoTime(); long elapsed = now - start; if (elapsed >= duration) { break; } duration -= elapsed; start = now; millis = duration / NANOS_PER_MILLI; nanos = (int) (duration % NANOS_PER_MILLI); } } } public static void sleep(long millis) throws InterruptedException { Thread.sleep(millis, 0); } @FastNative private static native void sleep(Object lock, long millis, int nanos) throws InterruptedException;
3.sleep与join的区别
- join里面调用的wait方法,wait方法可以释放锁,而sleep方法是持有锁
- join(0)是一直等待线程执行完成,只有这个线程执行完后,才能执行其他线程,中间通过循环lock.wait(delay)实现,它是非静态方法,
- sleep是静态方法,通过currentThread获取当前线程的lock,它只能作用当前线程
三、Thread终止
1.stop方法
stop方法以及被弃用,强行调用的话会抛出UnsupportedOperationException异常
@Deprecated
public final void stop() {
stop(new ThreadDeath());
}
@Deprecated
public final void stop(Throwable obj) {
throw new UnsupportedOperationException();
}
2.interrupt方法
部分内容引用一篇很详细的文章,戳-->《Java线程源码解析之interrupt》
interrupt的作用是中断线程,我们经常调用,interrupt的使用有几个注意点
当线程处于wait,sleep,join等方法阻塞状态时,它会清除当前阻塞状态,并抛出InterruptedException异常
在I/O通讯状态中调用interrupt,数据通道会被关闭,并将线程状态标记为中断,并抛出ClosedByInterruptException异常
如果在java.nio.channels.Selector上堵塞,会标记中断状态,并马上返回select方法
Lock.lock()方法不会响应中断,Lock.lockInterruptibly()方法则会响应中断并抛出异常,区别在于park()等待被唤醒时lock会继续执行park()来等待锁,而 lockInterruptibly会抛出异常
synchronized被唤醒后会尝试获取锁,失败则会通过循环继续park()等待,因此实际上是不会被interrupt()中断的;
一般情况下,抛出异常时,会清空Thread的interrupt状态,在编程时需要注意;
//用来中断的IO通讯对象,在调用interrupt方法后会调用blocker的中断方法
private volatile Interruptible blocker;
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
nativeInterrupt();
b.interrupt(this);
return;
}
}
nativeInterrupt();
}
四、线程的状态
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
public State getState() {
// get current thread state
return State.values()[nativeGetStatus(started)];
}
- NEW:线程创建还未启动时状态
- RUNNABLE:线程运行状态,包括一些系统资源等待,如:IO等待,CPU时间片切换等
- BLOCKED:正在等待monitor lock的状态,比如:1. 即将进入synchronized方法或者块前等待获取锁的这个临界时期状态。2.调用wait方法释放锁之后再次进入synchronized方法或者块前的临界状态
- WAITING:基于上个BLOCKED状态来说,WAITING就是拿到锁了,处于wait过程中的状态,注意它是特指无限期的等待,也就是join()或者wait()等,它是join或者直接wait方法当获取到lock执行后,处于等待notify的WAITING状态。
- TIMED_WAITING:与上面WAITING相对,WAITING是指无限期的等待,TIMED_WAITING就是有限期的等待状态,包括join(long),wait(long),sleep(long)等。
- TERMINATED:线程执行完成,run结束的状态
五、总结:回答上述问题
- 调用2次start时,看start源码中,里面判断如果当前线程状态和是否启动标记,
if (threadStatus != 0 || started)
,如果已经启动则抛出IllegalThreadStateException异常,可以通过继承Thread类或者实现Runnable去开启线程,这样每次new了新的对象启动线程 - start是启动当前Thread线程,Thread实现了Runnable接口的run方法,当线程启动,run方法会被调用,Thread里面的Run会调用传入Runnable Target的run方法,达到实现我们自定义任务的目的。如果没有传入Runnable参数则do nothing
- join是等待线程执行完成,方法通过内部一个while(alive)的循环函数去实现wait等待,alive是一直检测线程的存活状态,它相当于,在那个线程执行join,即在哪个线程执行wait,调用的线程对象可以理解为lock对象,即调用了lock.wait(), sleep方法是一直持有锁的状态,同时sleep是静态方法,它通过currentThread获取当前线程的lock,并只能作用当前线程
- 守护线程意思是后台服务线程,比如垃圾回收线程,要理解它就知道另一个用户线程,用户线程是维持程序运行状态,或者说jvm存活的线程,如果用户线程都跑完了,那么不管守护线程是否运行,程序和jvm都会退出,当然此时,守护线程也会退出,由此可以看出守护线程和用户线程对于程序运行的相关性。由上述线程的init方法可以看出,子线程的创建会继承一些默认参数,包含是否为守护线程,它是低级别的线程,不依赖于终端,但是依赖于系统,与系统“同生共死”。