构造函数
创建 Thread 对象的时间都会调用 init()
方法,取一个最常用的构造方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
// 获取当前线程
Thread parent = currentThread();
if (g == null) {
g = parent.getThreadGroup();
}
g.addUnstarted();
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();
}
init()
方法的第一个参数叫做线程组, Java 编程思想
书中提到,这个概念其实并没有什么叼用. 在构造方法中也可以看到传入的是 null
.
init()
方法开始就通过 currentThread()
获取了创建 Thread 对象的当前线程, 一般就是 main thread
.
后续用 traget
保存了提交的任务, 用 priority
保存了优先级, 用 daemon
保存了是否是守护(后台)线程. 其中必须要注意, priority
和 daemon
的值取的是创建 Thread 对象的当前进程的相应的值, 也就是继承了当前线程的相应的属性.
同时,还可以注意到, 在创建 Thread
对象时, 会默认创建一个名字, 名字格式类似 Thread-threadId
这样的形式.
执行线程
在 Java 编程中, 一个 Thread
对象通常就是指的一个执行线程(thread of execution
), 然而这个说法并不确切, 因为只有调用了 Thread
对象的 start()
方法后, 才会创建执行线程
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* <code>start</code> method) and the other thread (which executes its
* <code>run</code> method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*/
public synchronized void start() {
// 线程只有启动一次, 否则抛异常
if (threadStatus != 0 || started)
throw new IllegalThreadStateException();
group.add(this);
started = false;
try {
// 用当前的 Thread 对象创建执行线程
nativeCreate(this, stackSize, daemon);
started = true;
} finally {
// ...
}
}
可以看到, nativeCreate()
用当前 Thread
对象创建了一个执行线程. 这也就解释了为何 Thread
对象调用 start()
方法后, Thread
对象没有立即被回收, 而是等到执行完任务后才会回收, 因为执行线程使用了 Thread
对象.
为何要传入一个 Thread
对象? 因为执行线程要执行一个任务(Runnable
), 而 Thread
类正好实现了 Runbale
接口. 所以接下来就是调用 Thread
类的 run()
方法
当执行线程创建后, 会调用 Thread
对象的 run()
方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
从这里可以清楚看到, 如果创建 Thread
对象的时候, 不传入 Runnable
对象, 这个线程其实不会做任何事. 另外一方面, 如果是继承 Thread
类, 可以通过复写 run()
方法来执行任务.
优先级
创建 Thread
对象的时候, 会把当前线程的优先级赋予给 Thread
对象, 注意, 我说的是 Thread
对象, 不是线程. 真正设置线程优先级的方法为 setPriority()
public final void setPriority(int newPriority) {
// ..
if((g = getThreadGroup()) != null) {
// ...
synchronized(this) {
// 赋值给 thread.priority
this.priority = newPriority;
if (isAlive()) {
// 给执行线程设置优先级
nativeSetPriority(newPriority);
}
}
}
}
nativeSetPriority()
才是真正的给执行线程设置优先级, 所以如果不调用 setPriority()
方法, 创建 Thread
对象的时候, 其实压根就没有把线程设置优先级, 只是给 Thread
对象设置变量.
再来看看 getPriority()
方法
public final int getPriority() {
return priority;
}
我惊讶的发现, 这个获取的优先级居然不是执行线程的优先级, 而是 Thread
对象的优先级, 百撕不得骑姐 ~
优先级有三个, MIN_PRIORITY
, NORM_PRIORITY
, NORM_PRIORITY
. 调度器会倾向于让优先级高的线程先执行, 但是这并不意味着优先级低的线程将得不到机会执行(不然不就造成死锁了). 优先级应该理解为执行频率. 所以试图通过优先级操作多线程的执行顺序, 通常就是错误的做法.
后台线程
所谓后台(daemon
)线程, 也称为守护线程, 是指在程序运行的时候在后台提供一种通用服务的线程, 并且这种线程并不属于程序中不可或缺的部分. 因此当所有非后台线程执行结束时, 程序也就终止了, 同时会杀死进程中的所有后台线程.
先看看 setDaemon()
方法
/**
* Marks this thread as either a {@linkplain #isDaemon daemon} thread
* or a user thread. The Java Virtual Machine exits when the only
* threads running are all daemon threads.
*
* This method must be invoked before the thread is started.
*/
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
从注释中可以明白两点问题:
- 参数 on 的值决定了线程是后台线程还是用户线程
- 如果只有后台线程在运行, JVM 将会退出, 也就是程序终止了.
- 这个方法必须在
Thread
对象调用start()
方法之前调用. why? 可能是创建执行线程的时候, 要用到这个属性吧~
休眠
Thread 类的静态方法 sleep()
会让当前的执行线程在指定时间段内休眠, 例如
new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
首先 Thread 对象调用 start()
方法会创建一个执行线程, 然后执行 Thread 对象的 run()
方法, 这样 run()
方法就会新创建的执行线程中执行, 所以调用 Thread.Sleep(1000)
会让线程休眠 1s.
再看看源码
public static void sleep(long millis) throws InterruptedException {
Thread.sleep(millis, 0);
}
public static void sleep(long millis, int nanos)
throws InterruptedException {
// STEP1: 判断参数可用性
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);
}
// The JLS 3rd edition, section 17.9 says: "...sleep for zero
// time...need not have observable effects."
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;
// STEP2: 获取当前执行线程的锁
Object lock = currentThread().lock;
// Wait may return early, so loop until sleep duration passes.
// STEP3: 同步锁, 并执行无限循环来休眠执行线程, 直到休眠时间完毕
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);
}
}
}
@FastNative
private static native void sleep(Object lock, long millis, int nanos)
throws InterruptedException;
直接看两个参数的 sleep()
方法, 这个方法其实做了三件事:
- 判断参数可用性, 否则会抛出异常
- 获取当前线程的锁
- 同步锁, 执行循环来休眠线程,直到休眠时间完毕
第一步中会根据参数以及线程的中断标志, 会抛出参数异常(IllegalArgumentException
)和中断异常(InterruptedException
). 但是我们经常遇到的中断异常是在无限循环的时候, 调用的 native sleep()
方法抛出的, 它是响应线程中断而抛出的异常, 那么会有两个情况:
- 当线程正在休眠的时间, 调用了
thread.interrupt()
方法, 会出现中断异常 - 当调用了
thread.interrupt()
方法后, 再试图进入休眠, 也会出现中断异常
从第三步中还可以看出,休眠期间是不会释放当前 thread 的锁的, 这在并发资源竞争中很关键。
让步
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
*/
public static native void yield();
注释说明了几点问题:
-
yield()
方法只是暗示调度器,当前线程可以让出处理器,调度器也可以忽略这个暗示. 所以可以使用,并不能依赖它的效果 - 一般用于调度或者测试,可以用来复现问题。
加入线程
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("execute t1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 awaken");
}
});
Thread t2 = new Thread() {
@Override
public void run() {
System.out.println("execute t2");
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 awaken");
}
};
t1.start();
t2.start();
几乎同时启动了两个线程 t1 和 t2。 然而在 t2 执行的时候, 突然调用 t1.join()
让 t1 执行, t1 执行完毕后, t2 再继续。
执行结果如下
01-17 18:03:16.799 System.out: execute t1
01-17 18:03:16.799 System.out: execute t2
01-17 18:03:21.800 System.out: t1 awaken
01-17 18:03:21.800 System.out: t2 awaken
从 Log 可以看出, t2 确实等待 t1 休眠了5后,t2 才执行后面的代码。
看看源码
public final void join() throws InterruptedException {
join(0);
}
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*/
public final void join(long millis) throws InterruptedException {
synchronized (lock) {
// ...
if (millis == 0) {
while (isAlive()) {
lock.wait(0);
}
} else {
// ...
}
}
}
代码我做了省略,主要逻辑就是这样。 这里理解起来有点难,我可以把例子中代码改编下
Thread t2 = new Thread() {
@Override
public void run() {
System.out.println("execute t2");
try {
synchronized (t1.lock) {
// ...
if (millis == 0) {
while (t1.isAlive()) {
t1.lock.wait(0);
}
} else {
// ...
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 awaken");
}
};
这样看起来就舒服了, 首先通过 synchronized
获取 t1.lock
的锁,防止 t1 的并发问题。 然后,调用 t1.isAlive()
判断 t1 是否还存活着, 如果还存活着,就调用 t1.lock.wait(0)
来挂起 t2 线程,注意,是挂起 t2 线程, 不是 t1。 当 t1 执行完毕后,会调用 t1.lock.notifyAll()
方法来唤醒 t2 线程,从而让 t2 线程继续执行。
中断
前面提到过,执行线程休眠或准备休眠的时候,如果调用 thread.interrupte()
方法会产生中断异常(InterruptedException).
看看 interrupte()
源码
/**
* Interrupts this thread.
*
* <p> Unless the current thread is interrupting itself, which is
* always permitted, the {@link #checkAccess() checkAccess} method
* of this thread is invoked, which may cause a {@link
* SecurityException} to be thrown.
*
* <p> If this thread is blocked in an invocation of the {@link
* Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
* Object#wait(long, int) wait(long, int)} methods of the {@link Object}
* class, or of the {@link #join()}, {@link #join(long)}, {@link
* #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
* methods of this class, then its interrupt status will be cleared and it
* will receive an {@link InterruptedException}.
*
* <p> If this thread is blocked in an I/O operation upon an {@link
* java.nio.channels.InterruptibleChannel InterruptibleChannel}
* then the channel will be closed, the thread's interrupt
* status will be set, and the thread will receive a {@link
* java.nio.channels.ClosedByInterruptException}.
*
* <p> If this thread is blocked in a {@link java.nio.channels.Selector}
* then the thread's interrupt status will be set and it will return
* immediately from the selection operation, possibly with a non-zero
* value, just as if the selector's {@link
* java.nio.channels.Selector#wakeup wakeup} method were invoked.
*
* <p> If none of the previous conditions hold then this thread's interrupt
* status will be set. </p>
*
* <p> Interrupting a thread that is not alive need not have any effect.
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
nativeInterrupt();
b.interrupt(this);
return;
}
}
nativeInterrupt();
}
从注释中总结以下几点
-
Object
的wait()
方法,Thread
的sleep(
) 方法和join()
方法,会导致线程阻塞。 如果调用了interrupt()
方法,线程的中断标志(interrupt status
)将会被清除,线程也会收到InterruptedException
。 -
InterruptibleChannel
也会阻塞线程,如果调用了interrupt()
方法, 通道会被关闭,线程的中断标志将被设置,并且线程会收到ClosedByInterruptException
。 -
Selector
也会阻塞线程,当调用了interrupt()
方法后,线程的中断标志将被设置,并且会立即从selection operation
中返回,并且可以带有一个非零的值。 - 如果没有以上的任何一个条件,那么只会设置中断标志。
注释中已经列举了所有产生中断异常的情况,如果不是这种情况,肯定就不会产生中断异常,例如 I/O 造成的阻塞,synchronized
无法获取对象锁造成的阻塞, 都是不会产生中断异常的。 其实,有个小技巧,用 IDE 写代码的时候,如果提示要 catch 中断异常,那么相应的代码就会响应中断异常,如果没有提示,肯定就不响应了。
从上面的四点总结,还需要注意一个问题,就是中断标志
- 如果产生中断异常,线程退出,会清除中断标志
- 如果没有产生中断异常,就会设置中断标志
Thread 类还一个静态的 interrupted()
方法
/**
* Tests whether the current thread has been interrupted. The
* <i>interrupted status</i> of the thread is cleared by this method. In
* other words, if this method were to be called twice in succession, the
* second call would return false (unless the current thread were
* interrupted again, after the first call had cleared its interrupted
* status and before the second call had examined it).
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if the current thread has been interrupted;
* <code>false</code> otherwise.
* @see #isInterrupted()
* @revised 6.0
*/
@FastNative
public static native boolean interrupted();
Thread.interrupte()
方法比较有意思,它会检测当前线程是否已经被中断过,并且会清楚中断标志。 前面说过,当调用 thread.interrupte()
的时候, 如果没有碰到能产生中断的情况, 线程是不会抛出中断异常的, 那么我们可以用 Thread.interrupted()
方法检测中断标志,然后手动退出。
当然检测中断标志还有一个方法
/**
* Tests whether this thread has been interrupted. The <i>interrupted
* status</i> of the thread is unaffected by this method.
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if this thread has been interrupted;
* <code>false</code> otherwise.
* @see #interrupted()
* @revised 6.0
*/
@FastNative
public native boolean isInterrupted();
这个方法并不是静态方法,它与静态的 interrupted()
方法的唯一区别就是,不清除当前线程的中断标志。
互斥锁中断
从 interrupte()
方法的注释还有一点没提及, 虽然 synchronized
方法或者临界区的阻塞不可中断 ,但是 ReentrantLock
上阻塞具备可以中断的能力。
ReentrantLock lock = new ReentrantLock();
try {
mLock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
从抛出的异常就可以看到,ReentrantLock
产生的阻塞具备可中断的能力。
网友评论