并发通常是提高运行在单处理器上的程序的性能。当以单线程运行所有任务时,虽然不会因为切换线程节省资源,但一旦出现阻塞(IO请求等),则在阻塞接触前单线程会发生等待。如果采用并发可在此时切换任务节省时间。(所以没有任务会被阻塞的情况下在单处理器上使用并发并没有意义)
CPU轮流给每个线程任务分配其占用时间。(例外情况是程序运行在多个CPU之上)
创建线程的多种方法:
- 通过extends Thread实现:
public class MyThread extends Thread{
@Override
public void run() {
//do something here
}
}
//main中启动线程
new MyThread().start();
- 通过implements Runnable接口实现
public class MyRunnable implements Runnable{
@Override
public void run() {
//do something here
}
}
//main中启动线程
new Thread(new MyRunnable()).start();
- 通过implements Callable接口实现:
public class MyCallable implements Callable<Integer>{
private int id;
public MyCallable(int id) {
this.id=id;
}
/*Callable接口与Runnable不同点在于实现的是call方法
* 此方法可return定义好的Callable接口的泛型类型,如此处的Integer
* 而Runnable的run方法是void的
* */
@Override
public Integer call() throws Exception {
return id;
}
}
//main中启动线程
MyCallable myCallable=new MyCallable(1);
//FutureTask是一个实现了Runnable以及Callable的包装类,借助它来创建Thread
FutureTask<Integer> futureTask=new FutureTask<Integer>(myCallable);
new Thread(futureTask).start();
//可获取call的返回值,FutureTask是Future的实现类
futureTask.get();
- 通过线程池实现:
/*创建一个缓存线程池(执行过程中创建与与所需数量相同的线程,并在开始回收旧线程时停止创建。Cached线程池通常是合理的首选)
*/
ExecutorService executor=Executors.newCachedThreadPool();
/*创建一个固定大小的线程池,例如这里设置为5。假如我们for循环创建50个任务。则总是只会有5个线程在执行。
例如12345,如果2最先执行完毕,线程销毁,那么这时候才会加入新的线程执行13456
*/
ExecutorService executor=Executors.newFixedThreadPool(5);
/*创建一个单一线程池,相当于大小为1的固定线程池,例如我们for循环加入五十个任务,用单一线程池进行执行,会跑完一个任务才会执行下一个任务
*/
ExecutorService executor=Executors.newSingleThreadExecutor();
/*将任务加入线程池进行执行
*/
executor.execute(Runnable runnable);//return void
executor.submit(Callable<T> callable);//return Future<T>
/*取值前先进行判断,因为future.isDone()方法用于检查Future对象是否已经用callable返回值完成了参数化。完成了就可以get()到值,如果未完成时调用get(),则get()方法会阻塞至完成后执行。
*/
if(future.isDone){
future.get();
}
/*关闭线程池。例如我们在执行线程main中调用此方法意味着当前线程(main)将继续运行shutdown前我们提交的线程任务,并在所有任务完成后尽快退出。
*/
executor.shutdown();
总结
Thread是线程,而Runnable,Callable,以及通过extends Thread重写的run,均是相当于一个任务对象。
我们可以直接在Thread构造参数里传入Runnable任务来创建线程,也可以借助FutureTask来包装Callable创建线程。
线程池是事先设置一定数目的线程存储空间。通过execute加入Runnable任务,or通过submit加入Callable任务。
Callable任务与Runnable任务不同点在于,前者具有返回值。
可通过Future的get()获取。Future接口定义了cancel(),sCancelled(),isDone(),get()等方法用于控制和查询。
FutureTask是Future的具体实现类,(implements RunnableFuture,而RunnableFuture接口又extends Runnable,Future)
SingleThreadExecutor因为同一时刻只有一个线程在执行,所以也能解决共享资源的同步,但更推荐对资源本身进行同步处理(锁机制)