1.带返回值的线程
2.Executor
3.线程池作用
1.带返回值的线程
1.Callable接口
//java.util.concurrent.Callable
public interface Callable<V>
{
V call() throws Exception;
}
和Runnable接口相同,然后可以使用return语句,可以抛出异常这点的话,就是在call()方法里处理异常的时候可以选择当场捕获,或者抛出,然后到更上一层再处理吧,平时应该用得很少。
2.Future接口
//java.util.concurrent.Future
public interface Future<V>
{
V get();
V get(long timeout, TimeUnit unit);
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
}
Future是用来进一步对线程操纵的接口,首先,要将Future对象与线程进行绑定,然后可以通过Future对象完成一些功能。
Future对象的绑定是通过ExecutorService接口的submit()方法实现的,submit()方法传入一个Callable对象,返回一个Future对象,然后通过这个Future对象,可以获取Callable线程的返回值,完成情况等信息。
get()方法用来获得计算结果,需要等待到线程进行到return那一步,因此还有一个带参数限定时间的get()方法
cancel()用来取消任务的执行,已完成已取消等情况则直接返回false,而调用cancel()时,如果此任务还未开始,则取消任务(可以停掉synchronized的阻塞等待),如果已经开始执行,则看参数,如果为true意味着强制取消,false则让已开始任务执行完毕。
isDone()可以用来判断任务的进行状况,调用cancel()后isDone()就是true。而isCancelled()则用来判断是否是取消的任务,要是通过cancel()成功取消的任务,才会返回true.
<1>实验cancel()的取消功能
public class Main
{
public static void main(String[] args)throws Exception
{
Object o = new Object();
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(new Runnable() {
public void run() {
synchronized(o) {
try {
Thread.sleep(3000);
System.out.println("th1停3s");
}catch(Exception ex) {}
}
}
});
Future<String> future = executor.submit(new Callable<String>(){
public String call() {
synchronized(o) {
System.out.println("th2");
}
return "0";
}
});
future.cancel(false);
}
}
结果是未打印“th2”,在synchronized的阻塞队列时,被cancel()取消任务。
Future<?> future = executor.submit(new Runnable(){
public void run() {
synchronized(o) {
System.out.println("th2");
}
}
});
future.cancel(false);
将Callable对象改为Runnable对象后,发现会打印“th2”了,也就是停不掉Runnable对象。
<2>实验get()方法
public class Main
{
public static void main(String[] args)throws Exception
{
Object o = new Object();
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(new Runnable() {
public void run() {
synchronized(o) {
try {
Thread.sleep(5000);
System.out.println("th1停5s");
}catch(Exception ex) {}
}
}
});
Thread.sleep(500);
Future<String> future = executor.submit(new Callable<String>(){
public String call() {
synchronized(o) {
System.out.println("th2");
}
return "done";
}
});
//1 String s2 = future.get(3,TimeUnit.SECONDS);
//2 System.out.println("??");
String s1 = future.get();
System.out.println(s1);
// System.out.println(s2);
}
}
取消注释1,2。当1语句在等待时,2语句不执行,等同于阻塞,1语句等待3秒后超时,不再等待并抛出异常,主线程gg。
不取消注释1,2。get()一直等到Callable线程执行完,传出done。
总结:虽然ExecutorService的submit()也可以传入Runnable对象,但更多还是与Callable搭配,完成传值,任务取消这两大特点。
3.FutureTask
FutureTask是实现类,它实现了RunnableFuture接口,而RunnableFuture接口又实现了Runnable与Future接口。
因此,可以说FutureTask是Future的实现类(主接近Future的功能),拥有Future和Runnable的特性。
<1>先说Runnable的特性,这个其实感觉很鸡肋,要用的话,那只能自定义类,然后继承FutureTask,再去重写run()方法,所以完全可以忽略了。并且用这种方式,完全联系不上Future的特性,就是单纯实现Runnable接口,中间还插了几层类关系,因此知道它的继承关系就行了。
<2>再说Future的特性,可以从构造方法上体现出来。
FutureTask<V>(Callable<V> callable);
FutureTask<V>(Runnable runnable,V result);
显然,就是把Future的绑定过程放到了FutureTask的构造器中,这样,只要将构造好的FutureTask对象拿去执行器执行,然后继续用自己本身的get(),cancel(),就能操纵线程了。
<3>但是,用构造器,将Callable与Runnable对象封装到FutureTask里后,可以用Thread类来启动线程与得到返回值。如下:
public class Main
{
public static void main(String[] args)throws Exception
{
FutureTask<String> ft = new FutureTask<String>(new Callable<String>() {
public String call() {
try {
Thread.sleep(2000);
System.out.println("call");
}catch(Exception ex) {}
return "Str";
}
});
Thread t = new Thread(ft);
t.start();
System.out.println(ft.get());
}
}
2.Executor
结构图如下: Executor是顶级接口,然后执行器主要靠ExecutorService,真正搭建线程池在ThreadPoolExecutor,Executors有静态工厂方法来创建一些常用线程池。
1.ExecutorService
public interface ExecutorService
{
//sumbit()方法执行任务,返回Future对象
Future<T> submit(Callable<T> task);
Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
/*
* shutdown()方法开始停止线程池
* shutdown()执行完未执行完的任务,不接受新任务
* shutdownNow()尝试着中断正在执行的线程
*/
void shutdown();
List<Runnable> shutdownNow();
}
2.ThreadPoolExecutor
说明一些基本的线程池参数:
public class ThreadPoolExecutor
{
private volatile int corePoolSize; //最大核心线程数
private volatile int maximumPoolSize; //最大线程总数
private volatile long keepAliveTime; //非核心线程闲置超时时长
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue);
/**
* 传入新线程时:
* 核心线程未满则创建核心线程
* ->已满,总线程未满,则创建非核心线程
* ->已满,阻塞队列未满,则线程进入阻塞队列等待
* ->已满(阻塞队列有界),则抛出异常
*/
}
3.Executors
public class Executors
{
/**
* 线程数量固定(都为核心线程)
* 没有闲置超时
* 阻塞队列基于链表实现,无上界
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
* 无核心线程,总线程数max
* 有闲置超时,线程空闲60s即关闭
* 阻塞队列只做个中转,传入后即执行
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
/**
* 类似newFixedThreadPool(1)
* 按顺序执行传入线程
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
/**
* 预定执行的线程池
* 多了个schedule()方法,可以传入线程对象与延迟时间
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
}
3.线程池作用
创建和销毁线程会带来系统开销,如果程序中有创建了大量生命期很短的线程,则频繁创建销毁线程会很影响效率,因此此时该考虑使用线程池。除此之外,如果在程序中使用过多的线程,则会占用很大的系统资源,这种情况也需要避免。因此线程池的作用可以简单归纳为:
1.减少线程频繁创建和销毁的开销
2.限制并发线程的数量
3.对线程的执行进行管理,可以实现一些功能(顺序执行,延时执行)
<1>使用单线程池实现线程T1,T2,T3按顺序执行
public class Main
{
public static void main(String[] args)throws Exception
{
ExecutorService executor = Executors.newSingleThreadExecutor();
Thread th1 = new Thread(){
public void run() {
System.out.println("T1");
}
};
Thread th2 = new Thread(){
public void run() {
System.out.println("T2");
}
};
Thread th3 = new Thread(){
public void run() {
System.out.println("T3");
}
};
//execute()方法在Executor中定义,只能传入Runnable对象
executor.execute(th1);
executor.execute(th2);
executor.execute(th3);
}
}
<2>……