实现并发最直接的方式是在操作系统级别使用进程。
并发任务彼此隔离的编程语言,被称为函数型语言,例如:erlang。
Java的线程机制是抢占式的,这表示调度机制会周期性地中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都会分配到数量合理的时间去驱动它的任务。(协作多线程)
线程的底层机制:切分CUP时间,但通常你不需要考虑它。
使用线程机制是一种建立透明的,可扩展的程序的方法。
Thread.yield()的调用是对线程调度器的一种建议,它表明“我已经执行完全生命周期中最重要的部分,此刻正是切换给其他任务执行一段时间的大好时机。
main()创建Thread对象时,每个Thread都”注册“了自己,它的任务退出其run()并死亡之前,垃圾回收器无法清除它。一个线程会创建一个单独的执行线程,在对start()的调用完成之后,它仍会继续存在。
使用Executor(java.util.concurrent包),用于管理Thread对象,为客户端和任务执行之间提供一个间接层,由Executor这个中介对象负责执行任务。允许你管理异步任务的执行。
ExecutorService(具有服务生命周期的Executor)知道如何构建恰当的上下文来执行Runnable对象。
ExecutorService service = Executors.newCachedThreadPool();//or newFixedThreadPool(5_
for(int i = 0; i < 5; i++){
service.execute(new LiftOff());
}
service.shutdown();
创建Executor的线程(这里是main线程)会继续运行,直至Executor的shutdown()方法调用后,这个线程(main)将在Executor中所有任务完成之后尽快退出。
在任何线程池中,现有线程在可能的情况下,都会被自动复用。
SingleThreadExecutor:对于想在另一个线程中连续运行的任何事物来说,是很有用的。例如监听进入的套接字连接的任务,或希望在线程中运行的短任务(如更新本地或远程日志的小任务)。
从任务中产生返回值:Callable接口。它的类型参数表示是从方法call()中返回值,并且必须使用ExecutorService.submit()方法调用它,示例:
public class CallableDemo {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
List<Future<String>> list = new ArrayList<Future<String>>();
for(int i =0; i < 5; i++){
list.add(service.submit(new TaskWithResult(i)));
}
for(Future<String> future : list){
try{
//get() blocks until completion:
System.out.println(future.get());
}catch(Exception e){
e.printStackTrace();
}finally{
service.shutdown();
}
}
}
}
class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id) {
this.id = id;
}
@Override
public String call() throws Exception {
return "result of TaskWithResult " + id;
}
}
异常是不能跨线程传播回main(),必须在本地处理所有任务内部产生的异常。
优先级将该线程的重要性传递给了调度器。优先级较低的线程仅仅是执行的频率较低。试图通过设置优先级来调试线程运行顺序是不可靠的。
让步:yield(),让别的线程使用CPU。这只是一个暗示,没有任何机制保证它将会被采纳。
后台线程:指在程序运行的时候在后台提供一种通用服务的线程,并不属于程序中必要的部分。因此当非后台进程结束时,程序也会终止,同时会杀死进程中的所有后台线程。反过来,只要有非后台线程还在运行,程序就不会终止(如main是非后台线程)。
可以通过实现ThreadFactory来定制Executor创建的线程属性(后台,优先级,名称):