Java线程的一生
Java中的线程
线程的新建启动
Java程序在运行时,除业务所用的线程外,还有后台守护线程,用于JVM执行资源关闭垃圾回收监听机制等等。所以Java中的程序天生便是多线程的。
本章,将讲述Java中线程的使用。所谓"万物皆对象",在Java中,Thread类就是对线程的抽象。
那么首先,在Java中是如何启动线程的呢?有2种方法:①继承Thread类;②实现Runnable接口;
jdk1.5引入了第三种方式:实现Callable接口。
继承Thread类
首先编写一个对象,继承Thread类,代表一个线程。重写run()方法,编写该线程具体任务。
public class NewThread1 extends Thread{
@Override
public void run() {
System.out.println("NewThread1 extends Thread run...");
}
}
然后 main方法,初始化对象,调用start()方法,运行线程。
public class Main{
public static void main(String[] args){
NewThread1 newThread1 = new NewThread1();
newThread1.start();
}
}
实现Runnable接口
首先编写一个对象,实现Runnable接口。重写run()方法,编写需要线程具体执行的任务。
public class NewThread2 implements Runnable{
@Override
public void run() {
System.out.println("NewThread21 implements Runnable run...");
}
}
然后 main方法,初始化上面编写好的需要任务对象,以编写好的任务对象构造一个线程对象,调用start()方法,运行线程。
public class Main{
public static void main(String[] args){
NewThread2 newThread2 = new NewThread2();
new Thread(newThread2).start();
}
}
实现Callable接口
我们可以看到上述两种方式,启动线程的方法大致方向就是重写run()方法,new一个Thread类或其子类,调用start()方法。
但Callable的启动线程的方式与众不同。
- 它虽然也要重写方法,但重写的不是run(),是call();
- Callable的call()方法有泛型返回值,而Runnable与Thread的子类重写的run()方法不能有返回值;
- 上述两种方式只需要初始化自身或者自身构造一个Thread类,调用start()即可启动线程,但Callable创建线程需要FutureTask的支持。
public class NewCallable implements Callable<Integer>{
@Override
public Integer call() {
System.out.println("NewCallable implements Callable<Integer> call...");
return 1;
}
}
public class Main{
public static void main(String[] args){
NewCallable newca = new NewCallable();
//用FutureTask包装Callable
FutureTask<Integer> task = new FutureTask<>(newca);
new Thread(task).start();
}
}
疑惑
为什么要重写run()方法? 查看Thread源码,无论从注释还是从run()代码都能发现,当线程是Thread的子类时,run()方法并不会做任何操作;而Runnable是一个函数式接口,run()是抽象方法,依赖于实现类。
public class Thread implements Runnable{
/* What will be run. */
private Runnable target;
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
使用start()启动线程?
new一个Thread,此时只是单纯的在JVM创建一个Thread对象,并没有与实际操作系统进程中的线程挂钩,当调用start()方法,运行start0()这个本地方法,才与线程真正挂钩。
/**
* 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.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
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()能否多次调用? 答案是不能的,根据上述源码可以看到threadStatus != 0的异常抛出。当线程调用一次后,当前threadStatus也就不再等于0,再次调用就会抛出异常。
Callable与FutureTask
FutureTask可通过Callable构造。而FutureTask实现RunnableFuture接口;RunnableFuture接口继承Runnable接口,有抽象方法run();根据源码可以看到在FuntureTask中实现的run()方法内调用了call()方法。也就是说,Callable方式的实现,其本质仍是重写run()方法,调用start()启动线程。
start0()如何使Thread与线程真正挂钩
threadStatus启动线程后如何变化
待补充
public class FutureTask<V> implements RunnableFuture<V> {
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
}
线程的结束中止
讨论完线程的启动,那么,如何结束一个线程?又或者如何中止一个线程?Java中提供了一系列的方法。结束有stop()关闭,destroy(),suspend()挂起,resume();但查看源码能够发现系统给这些结束方法打上了@Deprecated注解,不推荐使用。其原因很简单:如suspend挂起方法,它的调用会将线程挂起,但并不释放该线程占用的资源,这可能引发死锁。stop()终结时会强制结束线程,并不关心线程占用的资源。
取代结束的是中断机制,interrupt()方法,正常停止线程使用。interrupted()与isInterrupted()判断当前线程是否被中断。
interrupte()、interrupted()、isInterrupted()
interrupte()方法的实质是修改线程的中断标志位,只是告诉线程这个地方有人要求中断。这也就是说明jdk线程是协作式,而不是抢占式。
interrupted()、isInterrupted()都是用来判定当前是否要求中断。其区别在于isInterrupted()方法是static方法,同时调用后会修改中断标志位为false。查看源码可以发现interrupted()与isInterrupted()的实质是调用本地方法isInterrupted()。ClearInterrupted入参确定是否重置中断状态,这也就是为什么isInterrupted()会修改中断标志位为false。
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);
守护线程
守护线程是一种支持型线程,用于程序后台中调度及支持性工作。当非守护线程,也就是用户线程全部停止时,守护线程随之结束。setDaemon() 可将线程设置为守护线程。守护线程中若有finally,其内容不一定会执行,也就是不能依靠finally来确保执行关闭或清理资源。
线程的就绪运行与阻塞
线程从启动到结束的中间过程是怎样的?当线程所占有的CPU时间片到期时是如何等待下一个时间片?在线程运行中可能引发死锁等线程安全问题,可能被挂起等等,在这些情况下当前线程并不会释放占用的资源,此时线程是什么状态?线程的生命周期有新建,就绪,运行,阻塞,死亡5种状态。他们的联系如下:
CPU时间片轮转,即使启用start(),当操作系统分配的CPU时间片未到的时候,线程也不会进入运行状态,而是就绪状态,等待时间片到达,获得执行权,才是运行状态。join() 方法可获取执行权,yield() 方法则会让出时间片。而sleep() 与wait() 会使线程进入阻塞状态。
线程优先级
优先级范围1~10,优先级越高,被分配CPU时间片的可能越高。setPriority() 可设置线程运行优先级。
yield()、join()
yield() 方法会让出时间片,将线程从运行状态转为就绪状态,等待时间片执行权。操作系统在选中下一个时间片分配对象时,仍可能选中该线程。调用yield()方法的线程并不会释放锁。
join() 方法会将调用的线程对象加入到当前线程,挂起当前线程,至加入的线程执行完后继续,即在线程B中调用了线程A的join()方法,那么,直到线程A执行完毕后,才会继续执行线程B。也就是将两个交替执行的线程合并为顺序执行。