代码实现方式:
MyRunnable
,实现Runnable
接口MyRunnable
对象为参数,构造线程Thread
实例public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("任务实现");
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
这种线程实现方式侧重于“线程”和“任务”的独立性,也就是概念的解藕,“线程”不关心“任务”的具体实现,“任务”也不关心“线程”的管理方式。
代码实现方式:
Thread
,重写run()
实现具体的线程任务MyThread
实例public class MyThread extends Thread {
@Override
public void run() {
System.out.println("任务实现");
}
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
这种线程实现方式侧重于“任务线程”的整体概念,实现类MyThread
既具备线程管理功能,又含有具体的任务实现。“线程”和“任务”同生共死,相依为命。
因为Java
不支持多继承,所以,如果实现类MyThread
还有另外的extends
需求,就不能采用这种线程实现方式了。
代码实现方式:
java.util.concurrent.Callable
接口,重写call()
实现具体任务MyCallable
对象实例为参数,构造出FutureTask
对象实例FutureTask
对象实例为参数,构造出Thread
对象实例FutureTask
对象的get()
阻塞当前线程,直到任务线程执行完毕,返回任务执行结果。public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("任务实现");
return "线程执行结果";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<>(new MyCallable());
Thread thread = new Thread(task);
thread.start();
String result = task.get();
System.out.println(result);
}
}
这种线程实现方式略显复杂,编码的时候会付出一定的代价,但是换来的结果是有价值的。不仅解藕了“线程”和“任务”的概念,并且“当前线程”还能获取到“任务线程”异步执行的结果。
也就是说,任务执行过程可控,这个特性在某些业务场景下非常具有实用价值。而这,也是前面两种线程实现方式很难达到的效果。
Java
线程池的整体框架主要是基于以下几个接口实现的:
java.util.concurrent.Executor
java.util.concurrent.ExecutorService
java.util.concurrent.ScheduledExecutorService
java.util.concurrent.ThreadFactory
java.util.concurrent.Callable
大多数情况下,开发者会借助java.util.concurrent.Executors
创建并管理线程池。
Executors
就是围绕线程池框架产生的一个工具类,可类比集合工具类java.util.Collections
,还有数组工具类java.util.Arrays
。都是JDK
提供给开发者的福利,具有很高实用价值的工具类,通常情况下,开发者无需重复造轮子。
Executors
可创建以下几类线程池:
package java.util.concurrent;
public class Executors {
// 单线程工作的线程池,任务按提交顺序依次执行
public static ExecutorService newSingleThreadExecutor() { ... }
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { ... }
// 最大线程数量固定的线程池,线程数量达到最大后保持不变
public static ExecutorService newFixedThreadPool(int nThreads) { ... }
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { ... }
// 线程空闲后可以缓存一段时间的线程池,最大线程数量没有限制,多线程并发场景下使用很危险!
public static ExecutorService newCachedThreadPool() { ... }
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { ... }
// 提交的任务可以被周期性执行的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { ... }
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) { ... }
// 单线程的线程池,周期性执行提交的任务
public static ScheduledExecutorService newSingleThreadScheduledExecutor() { ... }
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { ... }
// 线程抢占式执行提交任务的线程池,底层原理和上面的几种线程池不太一样
public static ExecutorService newWorkStealingPool(int parallelism) { ... }
public static ExecutorService newWorkStealingPool() { ... }
}
通过Executors
工具类创建线程池后,提交任务的方法有四个,一个定义在Executor
接口当中,三个定义在ExecutorService
接口当中:
public interface Executor {
void execute(Runnable command);
}
public interface ExecutorService extends Executor {
Future<?> submit(Runnable task);
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
}
单看这四个方法定义,就可以发现它们最大的区别在于方法返回值.通过execute
提交的任务没有返回值,通过submit
提交的任务有返回值。
使用完线程池后,还应该主动关闭线程池。关闭线程池的方法有两个,都定义在ExecutorService
接口里面:
public interface ExecutorService extends Executor {
// 线程池不再接收新任务,等待池中已有任务执行完毕后,销毁线程池资源
void shutdown();
// 线程池不再接收新任务,尽可能停止正在执行的任务,尚未执行的任务列表作为方法的返回值,销毁线程池资源
List<Runnable> shutdownNow();
}
需要注意的是,主动调用关闭线程池的方法后,线程池也不是一定就能很快的关闭,因为线程池可以被关闭的条件是池中没有处于活动状态的任务。
如果线程池正在执行的某些任务非常耗时,线程池也会等到这些耗时任务都执行完毕后再销毁资源。
如果线程池中某些正处于活动状态的任务不能响应线程停止的消息,无法被有效的停止,线程池就没办法关闭,也不能销毁资源。