基本线程机制
并发编程将程序划分为多个分离的、独立运行的任务。通过多线程机制,这些独立的任务中的每一个都将由执行线程来驱动。(就是Runnable的实现类与Thread的关系)一个线程就是在进程中的一个单一的顺序控制流。单个进程可以拥有多个并发执行的任务。
在使用线程的时候,CPU将轮流给每个任务分配其占用时间,每个任务都觉得自己一直在占用CPU,但事实上,CPU的时间被划分为一个个小片段分配给所有任务。简单的讲,就是CPU在这个很短的时间段内执行这个线程,在那个短时间段内执行哪一个线程,从微观上不同时,宏观上是同时执行多个任务。
Runnable和Thread
线程(Thread)可以驱动任务(Runnable实现类),这时就需要一种描述任务的方式,即实现Runnable接口,实现其run()方法。
通常,任务中的run()方法被写成无限循环的形式。除非某个条件使得run()终止,否则将永远运行下去。该线程任务未执行结束,代表线程不能死亡,垃圾回收器就无法清除它。(Thread区别于其他对象,当普通的对象失去引用的时候,垃圾回收器将会回收内存)
示例代码
public class ThreadAndRunnable implements Runnable {
private int countDown = 10;
private static int taskCount = 0; // 任务数
private final int id = taskCount++; // id用于区分多个任务实例
public String toString() {
return "id " + "(" + id + ") a " + (countDown > 0 ? countDown : "归零");
}
public void run() {
while(countDown-- > 0) {
System.out.println(this.toString());
Thread.yield();
}
}
public static void main(String[] args) {
Thread mThread = new Thread(new ThreadAndRunnable());
mThread.start();
}
}
注:开辟线程的能力不仅仅main()线程拥有,其他子线程也可以开辟其他线程。
Executor的使用
java.util.concurrent包中的Executor可以用于管理Thread对象。
public ThreadPoolExecutor(int corePoolSize, // 核心线程数
int maximunPoolSize, // 线程池容纳最大线程数
long keepAliveTime, // 非核心线程闲置的超时时长
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, //线程池的任务队列, execute提交的runnable对象
ThreadFactory threadFactory // 为线程池提供创建线程的功能)
注意,ExecutorService对象是使用静态的Executor方法创建的。
①CachedThreadPool将为每个任务创建线程 。 它是一种线程数量不定的线程池, 他只有非核心线程, 并且最大数目为 Integer.MAX_VALUE。当线程池的所有线程都处于活动状态时, 线程池会创建新的线程来处理新的任务, 否则利用空闲线程来处理新任务。 而空闲线程超时60后, 将会回收。 也就是说,当整个线程池都置于空闲状态后, 线程池的线程都会超时而被停止, 这个时候 CachedThreadExecutor 几乎不占用任何系统资源。
适合执行大量耗时较少的任务
示例代码
import java.util.concurrent.*;
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool(); // 创建管理Thread的Executor
for(int i = 0; i < 3; i++) {
exec.execute(new MyRunnable()); // 创建3个任务,扔进线程池
}
// 调用shutdown()将不能再往里面添加任务,否则抛RejectedExecutionException异常
exec.shutdown();
}
}
②FixedThreadPool,有限线程集,在创建线程池的时候指定线程数量,即限制在线程池中同时运行的线程数。若指定为1的话,同时又有99个任务,最后会造成99个任务按顺序执行,即执行完一个,下一个才开始执行。
线程固定的线程池,线程处于空闲状态时,并不会回收,除非线程池被关闭。 由于核心线程都不会被回收, 所以能更加快速响应外界请求。
public class FixedThreadPool {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(3); // 指定线程数量
for(int i = 0; i < 99; i++) {
exec.execute(new MyRunnable());
}
exec.shutdown();
}
}
③SingleThreadExecutor,即线程数量限制为1的FixedThreadPool,通常放入的是长期存活的任务。如果向SingleThreadExecutor提交多个任务,就像上面说的,这些任务将会排队。所有任务使用的是相同的线程。(代码与上面类似,不再重复贴出)
摘自书本,假设有大量的线程,它们运行的任务将使用文件系统,你可以用SingleThreadExecutor来运行这些线程,以确保任意时刻在任何线程中都只有唯一任务在运行,所以在这种方式中,你不需要在共享资源上处理同步。
④ ScheduleThreadPool
通过 newScheduledThreadPool 创建, 核心线程数量固定, 而非核心线程是没有限制的, 并且闲置时会被回收。 这类线程池主要用于执行定时任务和具有固定周期的重复任务
上面三个线程池使用的Runnable实现类
class MyRunnable implements Runnable {
private int countDown = 5;
private static int taskCount = 0;
private int id = taskCount++;
public String toString() {
return "Runnable id: " + "(" + id + ")" + " countDown:" + (countDown > 0 ? countDown : "归零");
}
public void run() {
while(countDown-- > 0) {
System.out.println(this.toString());
}
Thread.yield(); // 让步其他线程执行
}
}
从任务中产生返回值
Runnable是执行工作的任务,但它没有返回值。若希望任务完成时返回一个值,则需要实现Callable接口,而不是Runnable接口。指定实现Callable接口的范型(以确定call的返回值),并重写call方法。最后必须使用ExecutorService.submit()方法调用它。
submit()方法会产生Future对象,可以调用isDone()来查询Future是否完成,调用get()方法,获取执行结果。如果没有用isDone()检查就直接调用get()方法,将产生阻塞,直至结果被计算出来。
示例代码:
import java.util.concurrent.*;
import java.util.*;
public class CallableDemo {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
// 存放Future对象
ArrayList<Future<String>> futureList = new ArrayList<Future<String>>();
for(int i = 0; i < 10; i++) {
futureList.add(exec.submit(new MyCallable(i)));
}
for(Future<String> future: futureList) {
try
{
System.out.println(future.get());
}
catch (InterruptedException e) {
System.out.println(e);
}
catch (ExecutionException e) {
System.out.println(e);
} finally {
exec.shutdown();
}
}
}
}
class MyCallable implements Callable<String> {
private int id;
public MyCallable (int id) {
this.id = id;
}
public String call() {
return "result of MyCallable " + id;
}
}
结果截图: