实现并发最直接的方式是在操作系统级别使用进程,进程是运行在它自己的地址空间内的自包容的程序。多任务操作系统可以通过周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个进程。
并发提供了一个重要的组织结构上的好处,可以使程序设计极大地简化。
下面的LiftOff任务将显示发射之前的倒计时:
public class LiftOff implements Runnable{
protected int countDown=10;
private static int taskCount=0;
private final int id=taskCount++;
public LiftOff(){}
public LiftOff(int countDown){
this.countDown=countDown;
}
public String status(){
return "#"+id+"("+(countDown>0?countDown:"LiftOff")+").";
}
public void run(){
while(countDown-->0){
System.out.println(status());
Thread.yield();
}
}
public static void main(String[] args) {
LiftOff lo=new LiftOff();
lo.run();
}
}
不同任务的执行在线程被换进换出时混在了一起,这种交换是由线程调度器自动控制的。
如:
public class MoreBasicThreads {
public static void main(String[] args) {
for(int i=0;i<5;i++)
new Thread(new LiftOff()).start();;
System.out.println("Waiting for LiftOff");
}
}
Executor用来管理Thread对象,从而简化了并发编程。Executor在客户端和任务执行之间提供了一个间接层,与客户端直接执行任务不同,这个中介对象将执行任务。Executor可以用来管理异步任务的执行,而无须显式地管理线程的生命周期。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService exec=Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
exec.execute(new LiftOff());
exec.shutdown();
}
}
Runnable是执行工作的独立任务,但是它不返回任何值。如果希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口.
import java.util.concurrent.Callable;
public class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id){
this.id=id;
}
public String call(){
return "result of TaskWithResult"+id;
}
}
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableDemo {
public static void main(String[] args) {
ExecutorService exec=Executors.newCachedThreadPool();
ArrayList<Future<String>> results=new ArrayList<Future<String>>();
for(int i=0;i<5;i++)
results.add(exec.submit(new TaskWithResult(i)));
for(Future<String>fs:results)
try{
System.out.println(fs.get());
}catch(InterruptedException e){
System.out.println(e);
return;
}catch(ExecutionException e){
System.out.println(e);
}finally{
exec.shutdown();
}
}
}
影响任务行为的一种简单方法是调用sleep(),这将使任务中止给定的时间。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class SleepingTask extends LiftOff{
public void run(){
try{
while(countDown-- > 0){
System.out.println(status());
TimeUnit.MILLISECONDS.sleep(100);
}
}catch(InterruptedException e){
System.err.println("Interrupted");
}
}
public static void main(String[] args) {
ExecutorService exec=Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
exec.execute(new SleepingTask());
exec.shutdown();
}
}
附加:对sleep()的调用可以抛出InterruptedException异常,上述例子中,其早run()中被捕获,因为异常不能跨线程传播回main(),所以必须在本地处理在任务内部产生的异常。
上述任务是完美分布的,0到4,再回头0到4。这是很有意义的,在每个打印语句之后,每个任务都将要睡眠,这使得线程调度器可以切换到另一个线程,进而驱动另一个任务。
注意!:顺序行为依赖于底层的线程机制,这种机制在不同的操作系统之间是有差异的,因此,不能依赖于sleep()! 而必须控制任务执行的顺序。
---恢复内容结束---