Thread源码解析
创建和启动线程
创建线程的方法很多,但是启动的方法只有一个就是strat。
Runnale接口
我们看Thread类的定义知道,它实现了Runable接口
public class Thread implements Runnable {
...
}
```
Runnable接口定义如下
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
我们可以通过Lambda表达式来创建Runnable接口的实例
线程创建
在java中,创建一个线程,有且仅有一种方式:
创建一个Thread类实例,并调用它的start方法。
这四个参数分别是:
- ThreadGroup g(线程组)
- Runnable target (Runnable 对象)
- String name (线程的名字)
- long stackSize (为线程分配的栈的大小,若为0则表示忽略这个参数)
init
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
...
}
所以综上来看,我们最常用的也就两个参数:
- Runnable target (Runnable 对象)
- String name (线程的名字)
而对于线程的名字,其默认值为"Thread-" + nextThreadNum(), nextThreadNum方法又是什么呢:
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
它就是一个简单的递增计数器,所以如果创建线程时没有指定线程名,那线程名就会是: Thread-0, Thread-1, Thread-2, Thread-3, … 这玩意还是上锁的。。
至此,我们看到,虽然Thread类的构造函数有这么多,但对我们来说真正重要的参数只有一个:
Runnable target (Runnable 对象)
如果我们在线程构造时没有传入target(例如调用了无参构造函数),那么这个run方法就什么也不干。
启动线程
线程对象创建完了之后,接下来就是启动一个线程,在java中,启动一个线程必须调用线程的start方法
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
实际上调用strat的时候是创建了一个线程,这个线程帮我们玩run方法里面的东西,我们strat()的调用者,不用废话,是主线程。这个毋庸置疑。其实start是帮我们开了一个新的线程去跑run方法,仅此而已。
线程状态及常用方法
线程状态
在Thread类中, 线程状态是通过threadStatus属性以及State枚举类实现的:
在Thread内部有个东西叫做State类
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
public State getState() {
return sun.misc.VM.toThreadState(threadStatus);
}
很清晰的可以看出来,有6个状态。 他们之间的关系,我找了个官方的图。 RUNNABLE状态包含了我们通常所说的running和ready两种状态。 ’
常用方法
currentThread
源码中currentThread定义如下:
public static native Thread currentThread();
这个东西事实上很简单,谁在运行,返回的就是谁,例如:A线程在执行。返回的就是A,B就是B,Main就是Main。
线程是CPU最小的调度单位,只要代码再跑,那么就是有线程在执行,返回的就是当前执行的线程。
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);
}
这个比较鸡肋,没什么用。因为native方法只有一个参数。
wait
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException("nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
wait有无参的wait()方法,它调用的是wait(0),表示无限期等待,而sleep并没有无参数的方法,因为sleep(0)代表的是不睡,那么可能就是简简单单的给其他线程一个空子钻,这个没什么必要。注意, wait会释放掉监视器锁!!! wait会释放掉监视器锁!!! wait会释放掉监视器锁!!! 重要的话说三次,这也是它与sleep的区别。
yield
public static native void yield();
yield只是让步,至于到底会不会让,就不知道了,如果你使用sleep或者wait。那么线程状态可能会变成 TIMED_WAITING,使用yield,最多会使线程从running 变成 ready。
join
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) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
意思是等待当前线程终止。也可以指定时间,不知道默认上送0,一直等,等到地老天荒也要等。只要线程还活着,就一直等,等的是哪个线程,被等的又是哪个。 等人的是当前线程,被等的是调用者。
现在有两个线程,小A和MAIN, a.join(),那么等人的就是MAIN,等A终止之后再执行。
线程中断interrupt
isInterrupted 和 interrupted
java中对于中断的大部分操作无外乎以下两点:
- 设置或者清除中断标志位
- 抛出InterruptedException
在Thread线程类里面提供了获取中断标志位的接口:
private native boolean isInterrupted(boolean ClearInterrupted);
这是一个native方法,同时也是一个private方法,该方法除了能够返回当前线程的中断状态,还能根据ClearInterrupted参数来决定要不要重置中断标志位
Thread类提供了两个public方法来使用该native方法:
public boolean isInterrupted() {
return isInterrupted(false);
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
啥意思呢?意思就是当你调用isInterrupted方法的时候,我只能给你返回 调用者是状态是不是中断。其他什么也不做。
而interrupted是一个静态方法,所以它可以由Thread类直接调用,自然就是作用于当前正在执行的线程,所以函数内部使用了currentThread()方法,与isInterrupted()方法不同的是,它的ClearInterrupted参数为true,在返回线程中断状态的同时,重置了中断标识位。
interrupt
这个方法并不能直接打断执行线程,只是说把当前线程的中断标志设为true,抛出InterruptedException异常(当线程处于阻塞情况,你去打断他),当然,如果这个线程已经终止,那就没用了,因为人家都执行完毕了,你还有打断他的必要吗? 上源码。
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0();
b.interrupt(this);
return;
}
}
interrupt0();
}
打断一个线程
我们现在要打断一个线程,去调用interrupt方法不能直接打断它,有人告诉我,stop()方法,但是一个线程运行的好好的,直接二话不说打断,不预留任何空间,这好吗?这不好?是不是有点不讲武德了?至少,你要让我知道吧,让我有点准备吧?偷袭是不好的,你没有防御措施,更加不好。所以这个方法被废弃了,不太安全。
那我们要怎么有武德的去打断线程呢?
当然了,线程要以和为贵,要讲武德。当A线程调用了B.interrupt的时候,如果此时B不在阻塞,在正常上班,那么B的中断状态被设成了true,我们可以这样,让B中断。
while (Thread.interrupted() && Thread.currentThread().getName().contains("main")){
System.err.println("线程中断状态为true");
System.err.println("那就别跑了呗");
return;
}
但是一定要做点什么,不然下一次,B就会被复位,因为我用的是interrupted()。 如果用isInterrupted ,那就不会被复位了,但是一定要做事,不然他会永远卡在这个while循环出不来,如果用interrupted,如果不做事,下次interrupted就会被设为false,线程没有任何影响。
好了,那么我们应该如何打断一个阻塞的线程呢,等的时间太长,黄花菜都凉了,回家了,不等了。怎么办?
@Override
public void run() {
while(true) {
try {
} catch (InterruptedException ie) {
break;
}
}
通过捕获异常即可。
结束语
结束
|