文章将从以下几个方面介绍:
前言
Runnable 源码分析
Thread 源码分析
Callable 源码分析
Future 源码分析
FutureTask 源码分析
前言
在 Java 中,实现线程的方式主要有以下几种方式:继承 Thread, 实现 Runnable 和实现 Callable 这三种方式;采用哪种方式,主要根据实际情况而定,比如:因为 Java 是单继承,所以如果定义的线程还有其他父类的话,就可以使用实现 Runnable 的方式,如果定义的线程就只有 Thread 一个父类,就可以从用继承 Thread 的方式来声明线程;如果线程执行后需要有返回值,则可以采用实现 Callable 的方式来声明线程。
Runnable
Runnable 的源码如下:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Runnable 它是一个接口,只有一个 run 方法,当线程在执行的时候,会自动的执行该 run 方法,我们采用实现 Runnable 的方式声明线程的时候,就需要重写该 run 方法;该方式需要使用 Thread 类的 start 方法来启动线程。如下所示:
// 声明线程
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("implements Runnable");
}
}
// 启动线程
new Thread(new MyThread2()).start();
输出:implements Runnable
Thread
Thread 类本身就是一个线程,它实现了 Runnable 接口,它提供了很多的方法来控制线程的行为,类图如下:
每个线程都有优先级(priority),高优先级的线程会优于低优先级的线程执行,但并不是说高优先级的线程一定在低优先级的线程之前执行,只是获取到 CPU 的概率要大些。线程的优先级共有 10 个级别,最低级别为1,默认的级别为5,最高级别为10。
当Java虚拟机启动时,通常会有一个非守护程序线程(通常调用某个指定类的main方法)。 当在遇到如下任意情况之前,Java虚拟机会继续执行线程:
1. 调用 Runtime 类的 exit 方法,并且安全管理器允许执行退出操作
2. 所有非守护线程都已“死亡”
3. run 方法执行完毕
4. run 方法抛出异常。
下面来看下 Thread 类的源码,只会选一些常见的进行分析:
public class Thread implements Runnable {
// 优先级
private int priority;
// 是否单步执行该线程
private boolean single_step;
// 是否是守护线程,默认不是
private boolean daemon = false;
// 要运行的线程
private Runnable target;
// 线程组
private ThreadGroup group;
// 线程最低的优先级
public final static int MIN_PRIORITY = 1;
// 线程默认的优先级
public final static int NORM_PRIORITY = 5;
// 线程最大的优先级
public final static int MAX_PRIORITY = 10;
// 返回当前线程
public static native Thread currentThread();
/********************** 常见方法 *******************/
}
下面是 Thread 的一些常见方法:
yield()
/**
* 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 方法会告诉线程调度器,当前线程愿意放弃CPU的使用权,把CPU让给其他线程执行,当前线程会从执行状态变为可执行状态;但是,调度器可能会忽略该消息,也就是说,yield 方法有意愿放弃CPU的使用权,但是还得看调度器是否同意,即使 yield 已经成功的放弃了CPU的使用权,但是在下一轮调度的时候,还是会调度到它,让它继续执行;yield 方法主要是用来保证其他线程有机会执行而不至于会导致饥饿。一般很少使用该方法,但是它对于调试和测试可能很有用。
测试:
class MyThread1 extends Thread{
public MyThread1(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + i);
if (i == 30){
Thread.yield();
}
}
}
}
new MyThread1("thread-1 : ").start();
new MyThread1("thread-2 : ").start();
new MyThread1("thread-3 : ").start();
new MyThread1("thread-4 : ").start();
new MyThread1("thread-5 : ").start();
输出:
thread-4 : 28
thread-4 : 29
thread-4 : 30 // 此时,thread-4 应该放弃CPU使用权,可是它并没有放弃或者放弃了又再次被调度
thread-4 : 31
thread-4 : 32
........
thread-4 : 49
thread-1 : 0
........
thread-1 : 16
thread-5 : 0
........
thread-5 : 29
thread-5 : 30 // 此时, thread-5 放弃 CPU 的使用权,把机会留给 thread-1 执行
thread-1 : 17
thread-1 : 18
........
thread-1 : 29
thread-1 : 30 // 此时, thread-1 放弃 CPU 的使用权,把机会留给 thread-5 执行
thread-5 : 31
sleep()
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
*
* @param millis
* the length of time to sleep in milliseconds
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
sleep 方法导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数, 该线程不会释放已经拥有的锁。 如果其他的线程中断了一个休眠的线程,sleep方法会抛出Interrupted Exception。
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();
start 方法是用来启动一个线程,当调用 start 方法后,JVM 会自动去执行当前线程的 run 方法,从上述源码中可以看到,start 会执行 start0 方法,而 start0 方法是一个本地方法,run 方法应该在里面调用的吧。
start 方法只能调用一次,多次调用会出错。
interrupt()
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
interrupt 方法中断当前线程。如果在调用 wait 或 join 时阻塞了这个线程,那么它的中断状态将被清除,它将收到一个 InterruptedException。如果此线程在I / O操作中被阻塞,那么通道将关闭,线程的中断状态将被设置,并且线程将接收到 ClosedByInterruptExcetion。如果上述操作没有抛出异常,则将设置该线程的中断状态。
interrupt方法并不是强制终止线程,它只能设置线程的中断状态
interrupted()
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态)
isInterrupted()
public boolean isInterrupted() {
return isInterrupted(false);
}
测试线程是否已经中断。线程的中断状态不受该方法的影响。
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;
}
}
}
join 方法把指定线程加入到当前线程中执行,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
以上就是 Thread 类中的常见方法。
既然说到 sleep 方法,就会想到 Object 的 wait 方法。wait 方法也会是线程暂停执行,直到由 notify 或 notifyAll 进行唤醒。调用 wait 方法后,线程会释放掉锁。
Callable
Callable 也可以用来实现线程,采用 Callable 方式执行线程,我们可以得到线程的一个执行结果,线程的执行结果通过 Future 进行返回;
Callable 和 Runnable 类似,都是为了线程而设计,但是 Runnable 的 run 方法执行线程后不能返回结果,也不能抛出异常;而 Callable 的 call 方法可以有返回值和抛出异常。
先看下它的源码实现:
@FunctionalInterface
public interface Callable<V> {
/**
* 可以返回结果和抛出异常
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Callable 需要配合 ExecutorService 来进行使用,它提供了一系列的的 submit 来执行:
<T> Future<T> submit(Callable<T> task);
Future
一个 Future 代表着一个异步计算结果,它提供了一些方法去检查计算是否完成,等待其完成,以及检索计算结果等。接下来看下它的接口声明:
public interface Future<V> {
// 取消任务,如果任务已完成,则返回false;
// 参数mayInterruptIfRunning 表示是否允许取消正在执行的任务,true表示允许,false表示不允许,任务会继续执行
boolean cancel(boolean mayInterruptIfRunning);
// 是否取消成功
boolean isCancelled();
// 任务是否完成
boolean isDone();
// 返回计算结果,该方法会阻塞一直到任务计算完成
V get() throws InterruptedException, ExecutionException;
// 在一定时间内返回计算结果,超时则返回null
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
看下它的一个使用:
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "hello";
}
});
String result = future.get();
System.out.println(result); // hello
executorService.shutdown();
}
FutureTask
FutureTask 提供了 Future 类的一个基本实现,它的类图如下:
可以看到,FutureTask 还实现了 Runnable 接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
public FutureTask(Callable<V> callable) {}
public FutureTask(Runnable runnable, V result) {}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("hello");
return "hello";
}
});
executorService.submit(futureTask);
executorService.shutdown();
}
以上就是实现线程的几种方式。