典型回答
Java的线程是不允许启动两次的,第二次调用必然会抛出IllegalThreadStateException,这是一种运行时异常,多次调用start被认为是编程错误。
关于线程生命周期的不同状态,在Java 5以后,线程状态被明确定义在其公共内部枚举类型java.lang.Thread.State中,分别是:
public final native void wait(long timeout) throws InterruptedException;
在第二次调用start()方法的时候,线程可能处于终止或者其它(非NEW)状态,但是不论如何,都是不可以再次启动的。
知识扩展
1、Java中的线程
从操作系统的角度,可以简单认为,线程是系统调度的最小单元。一个进程可以包含多个线程,作为任务的真正运作者,有自己的栈(Stack)、寄存器(Register)、本地存储(Thread Local)等,但是会和进程内其它线程共享文件描述符、虚拟地址空间等。
在具体实现中,线程还分为内核线程、用户线程。Java的线程实现其实是与虚拟机相关的。对于我们最熟悉的Sun/Oracle JDK,其线程也经历了一个演进过程。基本上在Java 1.2之后,JDK已经抛弃了所谓的Green Thread,也就是用户调用的线程。现在的模型是一对一映射到操作系统内核线程。
总体上来说,Java语言得益于精细粒度的线程和相关的并发操作,其构建高扩展性的大型应用的能力已经毋庸置疑。但是其复杂性也提高了并发编程的门槛,近几年的Go语言等提供了协程(coroutine),大大提高了构建并发应用的效率。与此同时,Java也在Loom项目中,孕育新的类似轻量级用户线程(Fiber)等机制,也许在不久的将来就可以在新版JDK中使用到它。
2、使用Runnable
虽然可以通过直接扩展Thread类并实例化它的方式创建线程,但是我们建议使用Runnable。
Runnable task = () -> {System.out.println("Hello World!");}; Thread myThread = new Thread(task); myThread.start(); myThread.join();
Runnable的好处是,不会受Java不支持多重继承的限制,当我们需要重复执行相应逻辑时优点明显。而且,也能更好地与现代并发库中的Executor之类框架结合使用。比如将上面的start和join的逻辑完全写成下面的结构:
Future futurn = Executors.newFixedThreadPool(1) .submit(task) .get();
这样我们就不用操心线程的创建和管理,也能利用Future等机制更好地处理执行结果。线程生命周期通常和业务之间没有本质联系,混淆实现需求和业务需求,就会降低开发的效率。
3、影响线程状态的因素
这里列出了导致线程状态发生改变的操作:
4、慎用ThreadLocal
ThreadLocal是Java提供的一种保存线程私有信息的机制,因为其在整个线程生命周期内有效,所以可以方便地在一个线程关联的不同业务模块之间传递信息,比如事务ID、Cookie等上下文相关信息。
它的实现结构可以参考源码,数据存储于线程相关的ThreadLocalMap,其内部条目是弱引用:
static class ThreadLocalMap { static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal> k, Object v) { super(k); value = v; } } }
当Key为null时,该条目就变成“废弃条目”,相关“value”的回收,往往依赖于几个关键点,即set、remove、rehash。
通常弱引用都会和引用队列配合清理机制使用,但是ThreadLocal是个例外,它并没有这么做。这意味着,废弃项目的回收依赖于显式地触发,否则就要等待线程结束,进而回收相应ThreadLocalMap!这就是很多OOM的来源。所以通常都会建议,应用一定要自己负责remove,并且不要和线程池配合,因为worker线程往往是不会退出的。
【完】