你真的了解线程吗?创建线程的常用方式有哪些?为什么不能重复调用 Start 方法?什么是单继承的局限?生产者与消费者如何实现?
1. 进程与线程
1.1 进程
进程是系统资源(CPU、内存等)分配的最小单位,它是程序执行时的一个实例。
通常情况下,进程和程序、应用可以看作是同一个概念,但一个程序可能有多个进程,例如,你可以在一台电脑上同时打开多个 QQ。打开的每一个 QQ 都对应一个进程,但所有的这些进程都属于程序 QQ。如下图:
每一个程序运行时系统都会为其创建一个进程,并为其分配资源,把该进程放入进程就绪队列,当进程调度器选中它时,就会为它分配 CPU 时间,此时程序才开始真正运行。因此,从微观上来看,程序的每一次运行,都可以看作是 “代码加载→代码执行→执行完毕” 一个完整的过程 。
1.2 线程
线程是 CPU 调度的最小单位,是“轻量级”的进程。
1.3 进程与线程的关系
- 进程是线程的载体,一个进程至少有一个线程
- 系统分配给每一个进程的资源在进程间是不共享,但每一个进程中的所有线程共享系统分配给该进程的资源
- 进程的创建资源消耗较大,线程的创建几乎不需要任何资源,因为它所需要的资源,在创建进程的时候已经加载进来了
- 相比于进程间通信,线程间通信十分简单
举个生活中的例子,进程相当于双向四车道的马路,线程相当于这条马路上的一条车道。
2. 常用的创建线程的方法
通常情况下,创建线程的方法有两种:
- 继承 Thread 类
- 实现 Runnable 接口
2.1 继承 Thread 类
2.1.1 语法
class 类名称 extends Thread{
属性...;
方法...;
public void run(){
线程主体;
}
}
2.1.2 实例
//源码:
public class ThreadByExtends_201810252239 extends Thread {
public ThreadByExtends_201810252239() {}
public ThreadByExtends_201810252239(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王盖地虎");
t1.start();
}
}
//执行结果:
THREAD-A-天王盖地虎 0
THREAD-A-天王盖地虎 1
THREAD-A-天王盖地虎 2
THREAD-A-天王盖地虎 3
THREAD-A-天王盖地虎 4
2.2 实现 Runnable 接口
2.2.1 语法
class 类名称 implements Runnable{
属性...;
方法...;
public void run(){
线程主体;
}
}
2.2.2 实例
//源码:
public class ThreadByImplements_201810260617 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
ThreadByImplements_201810260617 r1 = new ThreadByImplements_201810260617();
Thread t1 = new Thread(r1, "THREAD-A-小鸡炖蘑菇");
t1.start();
}
}
//执行结果:
THREAD-A-小鸡炖蘑菇 0
THREAD-A-小鸡炖蘑菇 1
THREAD-A-小鸡炖蘑菇 2
THREAD-A-小鸡炖蘑菇 3
THREAD-A-小鸡炖蘑菇 4
2.3 相关问题
2.3.1 通过继承 Thread 类创建线程时,为什么一定要覆写 run 方法?
查看 run 方法在 Thread 类中的定义:
//源码:
@Override
public void run() {
if (target != null) {
target.run();
}
}
由 run 方法定义可知,默认情况下,run 方法的执行最终调用的是 runnable 接口中的方法,所以如果通过继承 Thread 实现线程的时候不覆写 run 方法,那这个线程最终什么事也没做。
2.3.2 启动线程的时候,为什么调用的是 start 方法而不是 run 方法?能直接调用 run 方法吗?
查看 start 方法在 Thread 类中的定义:
//源码:
public synchronized void start() {
...
boolean started = false;
try {
start0();
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 */
}
}
}
private native void start0();
由 start 方法定义可知,start 方法最终调用的是 start0 方法,在 start0 方法声明处使用了 native 关键字,该关键字表示调用本机操作系统函数,因此此处线程的启动必须通过 start 方法。
如果直接调用 run 方法会出现什么情况?
//源码:
public class ThreadByExtends_201810261951 extends Thread{
public ThreadByExtends_201810261951() {}
public ThreadByExtends_201810261951(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
ThreadByExtends_201810261951 t1 = new ThreadByExtends_201810261951("THREAD-A-宝塔镇河妖");
t1.start();
ThreadByExtends_201810261951 t2 = new ThreadByExtends_201810261951("THREAD-B-铁锅炖大鹅");
t2.run();
}
}
//执行结果:
main 0
main 1
main 2
THREAD-A-宝塔镇河妖 0
THREAD-A-宝塔镇河妖 1
main 3
main 4
THREAD-A-宝塔镇河妖 2
THREAD-A-宝塔镇河妖 3
THREAD-A-宝塔镇河妖 4
由执行结果可知,当直接调用 run 方法的时候,实际上和普通方法的调用并没有任何区别——方法的运行是在 run 方法的调用线程,而不是子线程。
2.3.3 为什么不能重复调用 start 方法?
如果直接调用两次 start 方法会出现什么情况呢?
//源码:
public ThreadByExtends_201810262030() {}
public ThreadByExtends_201810262030(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
ThreadByExtends_201810262030 t1 = new ThreadByExtends_201810262030("THREAD-A-宝塔镇河妖");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.start();
}
}
//执行结果:
THREAD-A-宝塔镇河妖 0
THREAD-A-宝塔镇河妖 1
THREAD-A-宝塔镇河妖 2
THREAD-A-宝塔镇河妖 3
THREAD-A-宝塔镇河妖 4
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:705)
at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810262030.main(ThreadByExtends_201810262030.java:42)
由执行结果可知,当直接调用两次 start 方法时,程序会抛出 IllegalThreadStateException 异常。
接下来,看看 start 方法在 Thread 类中是如何定义的:
//源码:
public synchronized void start() {
...
if (threadStatus != 0)
throw new IllegalThreadStateException();
...
}
private native void start0();
由 start 方法定义可知,当 threadStatus != 0 时,程序会抛出 IllegalThreadStateException()。那 threadStatus 对应的是什么呢?查看 threadStatus 在 Thread 中的定义:
//源码:
/* Java thread status for tools,
* initialized to indicate thread 'not yet started'
*/
private volatile int threadStatus = 0;
由 threadStatus 变量的注释可知,当线程还没有启动的时候,threadStatus = 0。另外,由 start 方法定义中的注释可知,当 threadStatus = 0 时,线程对应的状态是 NEW。那这个 NEW 是在哪里定义的呢?查看 Thread 类定义可知,NEW 是枚举类 State 的一个对象,枚举类 State 定义如下:
//源码:
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
由枚举类 State 定义可知,当 Thread 启动之后,便会进入其他状态。当线程执行完毕的时候,线程的状态会变成 TERMINATED 而不是 NEW,因此,重复调用 start 方法时,程序会抛异常。
为了验证上面的分析,将之前的代码略作修改:
//源码:
public class ThreadByExtends_201810262030 extends Thread{
public ThreadByExtends_201810262030() {}
public ThreadByExtends_201810262030(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
ThreadByExtends_201810262030 t1 = new ThreadByExtends_201810262030("THREAD-A-宝塔镇河妖");
System.out.println(t1.getState());
t1.start();
System.out.println(t1.getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t1.getState());
t1.start();
}
}
//执行结果:
NEW
RUNNABLE
THREAD-A-宝塔镇河妖 0
THREAD-A-宝塔镇河妖 1
THREAD-A-宝塔镇河妖 2
THREAD-A-宝塔镇河妖 3
THREAD-A-宝塔镇河妖 4
TERMINATED
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:705)
at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810262030.main(ThreadByExtends_201810262030.java:45)
由执行结果可知,当线程还没有调用 start 方法的时候,此时 State 对应的是 NEW;当线程调用 start 方法之后,此时 State 对应的是 RUNNABLE;当线程执行完毕之后,此时 State 对应的是 TERMINATED,即:当再次调用 start 方法时,State 对应的不是 NEW,故抛异常。
2.3.4 两种创建线程方式的联系及区别
2.3.4.1 联系
观察 Thread 类部分代码:
//源码:
public class Thread implements Runnable {
…
/* What will be run. */
private Runnable target;
…
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
…
this.target = target;
…
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
由 Thread 类定义可知,Thread 与 Runnable 实现类是代理的关系,Thread 是 Runnable 实现类的代理,这也解释了为什么两种实现方式最终都需要通过 Thread 启动线程。
image2.3.4.2 区别
- 继承 Thread 有单继承局限;
- 资源共享方式不同。
两种创建线程的方式在资源共享方面区别还是挺大的,不过由于资源共享涉及到同步问题。因此,将此部分概念放到 《死锁与同步》模块详述。
2.3.5 多个 Thread 使用同一个 Runnable 对象,最终创建的线程是同一个吗?它们之间什么关系?多个 Thread 使用多个 Runnable 呢?
2.3.5.1 多个 Thread 单个 Runnable
多个 Thread 使用同一个 Runnable 对象时,最终会创建多个 Thread 对象。由于多个 Thread 使用的是同一个 Runnable,所以,最终多个 Thread 之间数据共享,如:
//源码:
public class ThreadByImplements_201810262032 implements Runnable {
private int bandit = 10;
@Override
public void run() {
for (int i = 0; i < 25; i++) {
if (bandit > 0) {
System.out.println(Thread.currentThread().getName() + " 杀了 " + (bandit--) + " 土匪 " + this.hashCode());
}
}
}
public static void main(String[] args) {
ThreadByImplements_201810262032 r1 = new ThreadByImplements_201810262032();
new Thread(r1,"THREAD-张飞").start();
new Thread(r1,"THREAD-赵云").start();
new Thread(r1,"THREAD-吕布").start();
new Thread(r1,"THREAD-董卓").start();
new Thread(r1,"THREAD-孙尚香").start();
new Thread(r1,"THREAD-华佗").start();
new Thread(r1,"THREAD-曹操").start();
new Thread(r1,"THREAD-夏侯惇").start();
new Thread(r1).start();
new Thread(r1).start();
}
}
//执行结果:
THREAD-张飞 杀了 10 土匪 1578680387
THREAD-孙尚香 杀了 6 土匪 1578680387
THREAD-华佗 杀了 4 土匪 1578680387
THREAD-董卓 杀了 7 土匪 1578680387
THREAD-吕布 杀了 8 土匪 1578680387
THREAD-赵云 杀了 9 土匪 1578680387
THREAD-董卓 杀了 1 土匪 1578680387
THREAD-华佗 杀了 2 土匪 1578680387
THREAD-孙尚香 杀了 3 土匪 1578680387
THREAD-张飞 杀了 5 土匪 1578680387
上面的执行结果不仅验证了推论——多个 Thread 使用同一个 Runnable 时,多个 Thread 之间是数据共享的,同时还说明 Thread 对象的个数是由创建 Thread 对象的次数决定的,跟 Runnable 个数没有关系。
2.3.5.2 多个 Thread 多个 Runnable
多个 Thread 使用多个 Runnable 对象时,最终也会创建多个 Thread 对象。同样的,由于多个 Thread 没有使用同一个 Runnable 对象,所以,最终多个 Thread 之间数据不共享,如:
//源码:
public class ThreadByImplements_201810262032 implements Runnable {
private int bandit = 10;
@Override
public void run() {
for (int i = 0; i < 25; i++) {
if (bandit > 0) {
System.out.println(Thread.currentThread().getName() + " 杀了 " + (bandit--) + " 土匪 " + this.hashCode());
}
}
}
public static void main(String[] args) {
ThreadByImplements_201810262032 r1 = new ThreadByImplements_201810262032();
ThreadByImplements_201810262032 r2 = new ThreadByImplements_201810262032();
ThreadByImplements_201810262032 r3 = new ThreadByImplements_201810262032();
ThreadByImplements_201810262032 r4 = new ThreadByImplements_201810262032();
ThreadByImplements_201810262032 r5 = new ThreadByImplements_201810262032();
ThreadByImplements_201810262032 r6 = new ThreadByImplements_201810262032();
ThreadByImplements_201810262032 r7 = new ThreadByImplements_201810262032();
ThreadByImplements_201810262032 r8 = new ThreadByImplements_201810262032();
ThreadByImplements_201810262032 r9 = new ThreadByImplements_201810262032();
ThreadByImplements_201810262032 r10 = new ThreadByImplements_201810262032();
new Thread(r1,"THREAD-张飞").start();
new Thread(r2,"THREAD-赵云").start();
new Thread(r3,"THREAD-吕布").start();
new Thread(r4,"THREAD-董卓").start();
new Thread(r5,"THREAD-孙尚香").start();
new Thread(r6,"THREAD-华佗").start();
new Thread(r7,"THREAD-曹操").start();
new Thread(r8,"THREAD-夏侯惇").start();
new Thread(r9).start();
new Thread(r10).start();
}
}
//执行结果:
THREAD-赵云 杀了 10 土匪 258726645
THREAD-孙尚香 杀了 10 土匪 1513896901
THREAD-孙尚香 杀了 9 土匪 1513896901
THREAD-董卓 杀了 10 土匪 786514993
THREAD-吕布 杀了 10 土匪 441874245
THREAD-曹操 杀了 10 土匪 679081647
THREAD-曹操 杀了 9 土匪 679081647
THREAD-曹操 杀了 8 土匪 679081647
THREAD-张飞 杀了 10 土匪 1642210515
THREAD-曹操 杀了 7 土匪 679081647
THREAD-夏侯惇 杀了 10 土匪 1583687396
THREAD-吕布 杀了 9 土匪 441874245
THREAD-吕布 杀了 8 土匪 441874245
THREAD-董卓 杀了 9 土匪 786514993
THREAD-华佗 杀了 10 土匪 1785098644
THREAD-孙尚香 杀了 8 土匪 1513896901
…
2.3.6 什么是单继承局限?
一言以蔽之:只能继承一个父类。因为 Thread 是普通类,因此通过继承 Thread 类创建线程的时候,不能再继承其他类。相比于继承 Thread 类创建线程的方法,通过实现 Runnable 创建线程的方法显得更灵活,因为 Runnable 的实现类可以同时实现多个接口。
2.3.7 Thread.currentThread() 和 this.currentThread() 联系和区别
说实话,当我第一次被问到这个问题的时候有点懵逼,因为我也没有仔细想过这个问题。就像你女朋友有天突然问你“如果我跟你妈同时掉进河里,你先救谁?”一样,只是觉得有些突兀。其实只要稍微看下 currentThread 方法在 Thread 类中的定义就可以一目了然了。
//源码:
/**
* Returns a reference to the currently executing thread object.
*
* @return the currently executing thread.
*/
public static native Thread currentThread();
这个方法就是一个静态的调用本地方法的方法,没了。很多人不懂的地方可能不在于方法的定义上,而在于它们方法的使用上。
在使用上二者的区别在于,前者能用在所有的类(Thread 类、非 Thread 类)中,后者只能用在 Thread 中,与此同时,两者在 Thread 类中的使用效果并没有什么区别。
如果你还是不懂,那这样问可能就懂了:currentThread()、this.currentThread() 和 Thread.currentThread() 有什么区别?
//源码:
public class ThreadByExtend_201809052254 extends Thread {
public ThreadByExtend_201809052254() {}
public ThreadByExtend_201809052254(String n) {
super(n);
}
@Override
public void run() {
super.run();
System.out.println(currentThread().getName());
System.out.println(this.currentThread().getName());
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
ThreadByExtend_201809052254 t1 = new ThreadByExtend_201809052254("THREAD-A-天王盖地虎");
t1.start();
}
}
//执行结果:
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎
//源码:
public class ThreadByImplement_201809060656 implements Runnable {
@Override
public void run() {
//此处如果使用此函数系统会提示 The method currentThread() is undefined for the type ThreadByImplement_201809052225
// System.out.println(currentThread().getName());
//此处如果使用此函数系统会提示 The method currentThread() is undefined for the type ThreadByImplement_201809052225
// System.out.println(this.currentThread().getName());
//此函数无影响
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
ThreadByImplement_201809060656 r1 = new ThreadByImplement_201809060656();
Thread t1 = new Thread(r1,"THREAD-A-天王盖地虎");
Thread t2 = new Thread(r1,"THREAD-B-小鸡炖蘑菇");
Thread t3 = new Thread(r1,"THREAD-C-宝塔镇河妖");
t1.start();
t2.start();
t3.start();
}
}
//执行结果:
THREAD-A-天王盖地虎
THREAD-C-宝塔镇河妖
THREAD-B-小鸡炖蘑菇
这种问题产生的根本原因是基本概念不清,直接导致思考问题的出发点偏离正轨,进而导致行动错误,最终导致一些“神奇”的问题。
2.3.8 为什么不能调用 Stop?如何停止线程?
查看 stop 方法在 Thread 类中的定义:
//源码:
/**
* …
* @deprecated This method is inherently unsafe. Stopping a thread with
* Thread.stop causes it to unlock all of the monitors that it
* has locked (as a natural consequence of the unchecked
* <code>ThreadDeath</code> exception propagating up the stack). If
* any of the objects previously protected by these monitors were in
* an inconsistent state, the damaged objects become visible to
* other threads, potentially resulting in arbitrary behavior. Many
* uses of <code>stop</code> should be replaced by code that simply
* modifies some variable to indicate that the target thread should
* stop running. The target thread should check this variable
* regularly, and return from its run method in an orderly fashion
* if the variable indicates that it is to stop running. If the
* target thread waits for long periods (on a condition variable,
* for example), the <code>interrupt</code> method should be used to
* interrupt the wait.
*/
@Deprecated
public final void stop() {}
由 stop 方法注释(This method is inherently unsafe)可知,该方法在操作时会产生死锁的问题,因此不建议使用。
在 stop 方法的注释中,除了介绍为什么该方法不建议使用之外,还介绍了如何停止线程——通过变量控制,如:
//源码:
public class ThreadByExtends_201810281058 extends Thread{
private boolean mStop = false;
private int mNumber = 0;
public ThreadByExtends_201810281058() {}
public ThreadByExtends_201810281058(String n) {
super(n);
}
public boolean isStop() {
return mStop;
}
public void setStop(boolean stop) {
this.mStop = stop;
}
@Override
public void run() {
super.run();
while (!mStop) {
System.out.println(Thread.currentThread().getName() + " " + (++mNumber));
}
System.out.println(Thread.currentThread().getName() + " " + "停止了");
}
public static void main(String[] args) {
ThreadByExtends_201810281058 t1 = new ThreadByExtends_201810281058("THREAD-A-天王盖地虎");
t1.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.setStop(true);
}
}
//执行结果:
…
THREAD-A-天王盖地虎 26
THREAD-A-天王盖地虎 27
THREAD-A-天王盖地虎 28
THREAD-A-天王盖地虎 29
THREAD-A-天王盖地虎 30
THREAD-A-天王盖地虎 31
THREAD-A-天王盖地虎 停止了
由上面的示例可知,通过控制变量的方法完全可以达到停止线程的目的。
2.3.9 除了这种创建线程的方法,还有其他方法吗?是什么?
创建线程的方法除了上面提到的两种之外,还有其他方法,如: Callable + Future
//源码:
public class ThreadByCallable_201810281535 implements Callable<Integer>{
private int result;
@Override
public Integer call() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
result = i;
}
return result;
}
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
ThreadByCallable_201810281535 c1 = new ThreadByCallable_201810281535();
Future<Integer> result = executor.submit(c1);
executor.shutdown();
try {
System.out.println("Result " + result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//执行结果:
pool-1-thread-1 0
pool-1-thread-1 1
pool-1-thread-1 2
pool-1-thread-1 3
pool-1-thread-1 4
Result 4
由于其他创建线程的方法涉及本文未提到的知识,故在此不详述。
3. 线程的状态(生命周期)
任何一个线程都有五种状态,即:创建、就绪、运行、阻塞、终止。线程各状态之间的转换如下图所示:
image3.1 创建状态
使用 new 关键字创建完线程对象之后,线程就进入该状态。也就是说,系统给 Thread 对象分配了内存之后,这个对象就处于这个状态了。
3.2 就绪状态
当 Thread 对象调用 start 方法之后,线程就进入该状态。处于这个状态的线程将进入线程队列排队,等待 CPU 的调度。
3.3 运行状态
当线程队列中的线程被 CPU 调度的时候,线程就进入该状态。此时将执行线程的 run 方法。
3.4 阻塞状态
当处于运行状态的线程调用 suspend、sleep、wait 等方法时,线程就进入该状态。处于阻塞状态的线程不能进入线程队列排队,只有当引起阻塞的原因消失之后,线程才能进入线程队列排队。
3.5 阻终止状态
当线程调用 stop 方法或者 run 方法执行完毕之后,线程就进入该状态。处于该状态的线程不能再运行,详情请参考《2.3.3 为什么不能重复调用 start 方法?》。
4. 线程常用方法
4.1 线程名
4.1.1 语法
public final String getName()
4.1.2 示例
//源码:
public class ThreadByExtends_201810281917 extends Thread{
public ThreadByExtends_201810281917() {}
public ThreadByExtends_201810281917(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王盖地虎");
ThreadByExtends_201810252239 t2 = new ThreadByExtends_201810252239("THREAD-B-宝塔镇河妖");
ThreadByExtends_201810252239 t3 = new ThreadByExtends_201810252239("THREAD-C-小鸡炖蘑菇");
ThreadByExtends_201810252239 t4 = new ThreadByExtends_201810252239();
ThreadByExtends_201810252239 t5 = new ThreadByExtends_201810252239();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
//执行结果:
THREAD-A-天王盖地虎 0
Thread-0 0
Thread-0 1
THREAD-B-宝塔镇河妖 0
THREAD-B-宝塔镇河妖 1
THREAD-B-宝塔镇河妖 2
THREAD-C-小鸡炖蘑菇 0
THREAD-C-小鸡炖蘑菇 1
THREAD-B-宝塔镇河妖 3
Thread-0 2
Thread-0 3
Thread-0 4
Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 3
THREAD-A-天王盖地虎 1
THREAD-A-天王盖地虎 2
THREAD-A-天王盖地虎 3
THREAD-A-天王盖地虎 4
Thread-1 4
THREAD-B-宝塔镇河妖 4
THREAD-C-小鸡炖蘑菇 2
THREAD-C-小鸡炖蘑菇 3
THREAD-C-小鸡炖蘑菇 4
4.2 状态检查
4.2.1 语法
public final native boolean isAlive()
4.2.2 示例
//源码:
public class ThreadByExtends_201810281928 extends Thread{
public ThreadByExtends_201810281928() {}
public ThreadByExtends_201810281928(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王盖地虎");
System.out.println("Is Alive" + " " + t1.isAlive());
t1.start();
System.out.println("Is Alive" + " " + t1.isAlive());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Is Alive" + " " + t1.isAlive());
}
}
//执行结果:
Is Alive false
Is Alive true
THREAD-A-天王盖地虎 0
THREAD-A-天王盖地虎 1
THREAD-A-天王盖地虎 2
THREAD-A-天王盖地虎 3
THREAD-A-天王盖地虎 4
Is Alive false
4.3 强制运行
4.3.1 语法
public final native boolean isAlive()
4.3.2 示例
//源码:
//1. Normal
public class ThreadByExtends_201810281950 extends Thread{
public ThreadByExtends_201810281950() {}
public ThreadByExtends_201810281950(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
outputNormal();
// outputJoin();
}
private static void outputNormal(){
ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println("Main" + " " + i);
}
}
private static void outputJoin(){
ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
t1.start();
for (int i = 0; i < 10; i++) {
if(i == 5){
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Main" + " " + i);
}
}
}
//执行结果:
Main 0
Main 1
Main 2
THREAD-A-天王盖地虎 0
THREAD-A-天王盖地虎 1
THREAD-A-天王盖地虎 2
Main 3
Main 4
THREAD-A-天王盖地虎 3
THREAD-A-天王盖地虎 4
Main 5
THREAD-A-天王盖地虎 5
THREAD-A-天王盖地虎 6
THREAD-A-天王盖地虎 7
Main 6
Main 7
Main 8
THREAD-A-天王盖地虎 8
Main 9
THREAD-A-天王盖地虎 9
//源码:
//2. Join
public class ThreadByExtends_201810281950 extends Thread{
public ThreadByExtends_201810281950() {}
public ThreadByExtends_201810281950(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
// outputNormal();
outputJoin();
}
private static void outputNormal(){
ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println("Main" + " " + i);
}
}
private static void outputJoin(){
ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
t1.start();
for (int i = 0; i < 10; i++) {
if(i == 5){
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Main" + " " + i);
}
}
}
//执行结果:
Main 0
Main 1
THREAD-A-天王盖地虎 0
THREAD-A-天王盖地虎 1
Main 2
Main 3
Main 4
THREAD-A-天王盖地虎 2
THREAD-A-天王盖地虎 3
THREAD-A-天王盖地虎 4
THREAD-A-天王盖地虎 5
THREAD-A-天王盖地虎 6
THREAD-A-天王盖地虎 7
THREAD-A-天王盖地虎 8
THREAD-A-天王盖地虎 9
Main 5
Main 6
Main 7
Main 8
Main 9
由上面的示例可知,当线程对象调用 join 方法之后,其他正在运行的线程必须让出 CPU,等待此线程完成之后才可以继续运行。
4.4 休眠
4.4.1 语法
public static native void sleep(long millis)
4.4.2 示例
//源码:
public class ThreadByExtends_201810282125 extends Thread{
public ThreadByExtends_201810282125() {}
public ThreadByExtends_201810282125(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
ThreadByExtends_201810282125 t1 = new ThreadByExtends_201810282125("THREAD-A-天王盖地虎");
t1.start();
}
}
//执行结果:
THREAD-A-天王盖地虎 0
THREAD-A-天王盖地虎 1
THREAD-A-天王盖地虎 2
THREAD-A-天王盖地虎 3
THREAD-A-天王盖地虎 4
从上面的执行结果里是不是感觉不到线程休眠的效果,那就用心去感受吧,少年。
4.5 中断执行
4.5.1 语法
public void interrupt()
4.5.2 示例
//源码:
public class ThreadByExtends_201810282135 extends Thread{
public ThreadByExtends_201810282135() {}
public ThreadByExtends_201810282135(String n) {
super(n);
}
@Override
public void run() {
super.run();
System.out.println(Thread.currentThread().getName() + " 1.开始休眠");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " *.休眠中断");
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 2.休眠结束");
}
public static void main(String[] args) {
ThreadByExtends_201810282135 t1 = new ThreadByExtends_201810282135("THREAD-A-天王盖地虎");
t1.start();
try {
Thread.sleep(200);
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//执行结果:
THREAD-A-天王盖地虎 1.开始休眠
THREAD-A-天王盖地虎 *.休眠中断
java.lang.InterruptedException: sleep interrupted
THREAD-A-天王盖地虎 2.休眠结束
at java.lang.Thread.sleep(Native Method)
at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810282135.run(ThreadByExtends_201810282135.java:31)
4.6 后台运行
4.6.1 语法
public final void setDaemon(boolean on)
4.6.2 示例
//源码:
public class ThreadByExtends_201810282143 extends Thread{
public ThreadByExtends_201810282143() {}
public ThreadByExtends_201810282143(String n) {
super(n);
}
@Override
public void run() {
super.run();
while (true) {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ThreadByExtends_201810282143 t1 = new ThreadByExtends_201810282143("THREAD-A-天王盖地虎");
t1.setDaemon(true);
t1.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t1.getState());
}
}
//执行结果:
…
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎
RUNNABLE
THREAD-A-天王盖地虎
THREAD-A-天王盖地虎
…
由上面的示例可知,线程主体里面的执行的是死循环,因此,正常情况下,Eclipse 的 Console 会一直输出线程的名字。但由于在启动线程之前调用了 setDaemon 方法,所以,虽然线程的主体是一个死循环,但 Java 进程依然可以结束。
下图就是上面示例控制台输出的数据,不难看出,此时的 Java 进程已经结束,但从日志输出中可以看出,此时的线程处于 RUNNABLE 状态,即:虽然进程结束了,但后台线程仍然在运行。
image4.7 优先级
4.7.1 语法
public final void setPriority(int newPriority)
4.7.2 示例
//源码:
public class ThreadByExtends_201810292043 extends Thread {
public ThreadByExtends_201810292043() {}
public ThreadByExtends_201810292043(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
ThreadByExtends_201810292043 t1 = new ThreadByExtends_201810292043("THREAD-A-天王盖地虎");
ThreadByExtends_201810292043 t2 = new ThreadByExtends_201810292043("THREAD-B-宝塔镇河妖");
ThreadByExtends_201810292043 t3 = new ThreadByExtends_201810292043("THREAD-C-小鸡炖蘑菇");
t1.setPriority(MIN_PRIORITY);
t2.setPriority(NORM_PRIORITY);
t3.setPriority(MAX_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
//执行结果:
THREAD-B-宝塔镇河妖 0
THREAD-B-宝塔镇河妖 1
THREAD-B-宝塔镇河妖 2
THREAD-C-小鸡炖蘑菇 0
THREAD-C-小鸡炖蘑菇 1
THREAD-C-小鸡炖蘑菇 2
THREAD-A-天王盖地虎 0
THREAD-A-天王盖地虎 1
THREAD-A-天王盖地虎 2
THREAD-A-天王盖地虎 3
THREAD-C-小鸡炖蘑菇 3
THREAD-B-宝塔镇河妖 3
THREAD-B-宝塔镇河妖 4
THREAD-C-小鸡炖蘑菇 4
THREAD-A-天王盖地虎 4
由上面的示例可知,虽然线程的给各线程都设置了优先级,但并不代表优先级高就一定先执行,最终决定哪个线程先执行的还是 CPU。
4.8 礼让
4.8.1 语法
public static native void yield()
4.8.2 示例
//源码:
public class ThreadByExtends_201810292053 extends Thread {
public ThreadByExtends_201810292053() {}
public ThreadByExtends_201810292053(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 2){
System.out.println(Thread.currentThread().getName() + " " + "线程礼让");
Thread.currentThread().yield();
}
}
}
public static void main(String[] args) {
ThreadByExtends_201810292053 t1 = new ThreadByExtends_201810292053("THREAD-A-天王盖地虎");
ThreadByExtends_201810292053 t2 = new ThreadByExtends_201810292053("THREAD-B-宝塔镇河妖");
ThreadByExtends_201810292053 t3 = new ThreadByExtends_201810292053("THREAD-C-小鸡炖蘑菇");
t1.start();
t2.start();
t3.start();
}
}
//执行结果:
THREAD-A-天王盖地虎 0
THREAD-B-宝塔镇河妖 0
THREAD-B-宝塔镇河妖 1
THREAD-C-小鸡炖蘑菇 0
THREAD-C-小鸡炖蘑菇 1
THREAD-B-宝塔镇河妖 2
THREAD-A-天王盖地虎 1
THREAD-B-宝塔镇河妖 线程礼让
THREAD-C-小鸡炖蘑菇 2
THREAD-C-小鸡炖蘑菇 线程礼让
THREAD-B-宝塔镇河妖 3
THREAD-A-天王盖地虎 2
THREAD-A-天王盖地虎 线程礼让
THREAD-B-宝塔镇河妖 4
THREAD-C-小鸡炖蘑菇 3
THREAD-A-天王盖地虎 3
THREAD-C-小鸡炖蘑菇 4
THREAD-A-天王盖地虎 4
由上面的示例可知,每当线程主体满足条件(i == 2)时,都会让出 CPU,让其他线程先执行,然后直接进入线程队列等待调度。
4.9 相关问题
4.9.1 线程自动命名是如何实现的?
创建线程对象的时候,如果不在构造方法里面传线程名,那么系统将会自动为其命名,如:
//源码:
public class ThreadByExtends_201810292216 extends Thread {
public ThreadByExtends_201810292216() {}
public ThreadByExtends_201810292216(String n) {
super(n);
}
@Override
public void run() {
super.run();
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
ThreadByExtends_201810292216 t1 = new ThreadByExtends_201810292216("THREAD-A-天王盖地虎");
ThreadByExtends_201810292216 t2 = new ThreadByExtends_201810292216();
ThreadByExtends_201810292216 t3 = new ThreadByExtends_201810292216();
t1.start();
t2.start();
t3.start();
}
}
//执行结果:
THREAD-A-天王盖地虎
Thread-1
Thread-0
一起看下 Thread 构造方法的定义:
//源码:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
Thread 构造方法中调用了 nextThreadNum 方法,nextThreadNum 定义如下:
//源码:
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
每次,当构造方法中没有传入线程名的时候,threadInitNumber 都会 +1,因此最终实现了上面例子所示的结果。
4.9.2 子线程可以比主线程晚消失吗?
可以,如:
//源码:
public class ThreadByExtends_201810292234 extends Thread {
public ThreadByExtends_201810292234() {}
public ThreadByExtends_201810292234(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadByExtends_201810292234 t1 = new ThreadByExtends_201810292234("THREAD-A-天王盖地虎");
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
//执行结果:
main 0
THREAD-A-天王盖地虎 0
main 1
main 2
main 3
main 4
THREAD-A-天王盖地虎 1
THREAD-A-天王盖地虎 2
THREAD-A-天王盖地虎 3
THREAD-A-天王盖地虎 4
4.9.3 多个线程同时强制运行(join)的时候,哪个先执行?
主线程中,有多个线程同时强制运行时,此时主线程会让出 CPU,直至强制运行的线程执行完才开始继续执行。强制运行线程的执行顺序并没有“先来后到”之分,都是由 CPU 决定的,CPU 想调哪个就执行哪个,即:此时的线程和普通的线程没有任何区别。
//源码:
public class ThreadByExtends_201810281950 extends Thread{
public ThreadByExtends_201810281950() {}
public ThreadByExtends_201810281950(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
outputJoinMulti1();
}
private static void outputJoinMulti1(){
ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王盖地虎");
ThreadByExtends_201810281950 t2 = new ThreadByExtends_201810281950("THREAD-B-宝塔镇河妖");
ThreadByExtends_201810281950 t3 = new ThreadByExtends_201810281950("THREAD-C-小鸡炖蘑菇");
t1.start();
t2.start();
t3.start();
for (int i = 0; i < 10; i++) {
if(i == 5){
try {
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Main" + " " + i);
}
}
}
//执行结果:
…
THREAD-B-宝塔镇河妖 6
Main 3
THREAD-A-天王盖地虎 2
Main 4
THREAD-B-宝塔镇河妖 7
THREAD-C-小鸡炖蘑菇 3
THREAD-B-宝塔镇河妖 8
THREAD-C-小鸡炖蘑菇 4
THREAD-A-天王盖地虎 3
THREAD-A-天王盖地虎 4
THREAD-C-小鸡炖蘑菇 5
THREAD-B-宝塔镇河妖 9
THREAD-C-小鸡炖蘑菇 6
THREAD-A-天王盖地虎 5
THREAD-C-小鸡炖蘑菇 7
THREAD-A-天王盖地虎 6
THREAD-C-小鸡炖蘑菇 8
THREAD-A-天王盖地虎 7
THREAD-C-小鸡炖蘑菇 9
THREAD-A-天王盖地虎 8
THREAD-A-天王盖地虎 9
Main 5
Main 6
Main 7
Main 8
Main 9
4.9.4 后台运行(daemon)的作用?Thread.setDaemon 之后就可以执行完了?
由 《4.6 后台运行》 的讲解可知,线程在启动之前,如果调用了 setDaemon 方法,那即使线程主体内是死循环,此时的 Java 进程也可以结束,但此时的线程并没有结束——依然在运行(RUNNABLE)。
4.9.5 主方法的优先级
//源码:
public class ThreadByExtends_201810292043 extends Thread {
public ThreadByExtends_201810292043() {}
public ThreadByExtends_201810292043(String n) {
super(n);
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + " " + Thread.currentThread().getPriority());
}
}
//执行结果:
main 5
//源码:
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
由 Thread 源码可知,线程优先级 Priority = 5 对应着 NORM_PRIORITY。
4.9.6 线程礼让,如果是多个线程而非两个,会出现什么情况?
Thread 类的 yield 方法并不会产生阻塞,只是让出这一次的 CPU 时间片。让出 CPU 时间片之后,Thread 对象立刻又到线程队列中排队等待调度了,而最终等待调度的线程哪个先执行,还是由 CPU 决定。因此,在主线程中,如果有多个线程礼让,礼让之后,哪个线程先执行,最终由 CPU 决定。
参考文档
1)《Java 开发实战经典》
2)《Thinking in Java》
3)Android Developer Document
4)Java Tutorials
网友评论