用并发解决的问题大体上可以分为“速度”和“设计可管理性”两种。
在run()中对静态方法Thread.yield()的调用是对线程调度器(java线程机制的一部分,可以将CPU从一个线程转移给另一个线程)的一种建议,它在声明:我已经执行完生命周期中最重要的部分了,此刻正是切换给其他任务执行一段时间的大好时机。ps:其实就是我停会,让别的线程运行会。
将Runnable对象转化为工作任务的传统方式是把它提交给一个Thread构造器,下面是例子:
Thread t = new Thread(new LiftOff());
t.start();
Thread构造器只需要一个Runnable对象。调用Thread对象的start()方法为该线程执行必须的初始化操作,然后调用Runnable的run()方法,以便在这个新线程中启动该任务。
使用Executor
Executor允许你管理异步任务的执行。我们可以使用Executor来代替上面的Thread对象。LiftOff对象知道如何运行具体的任务,与命令模式一样,它暴露了要执行的单一方法。
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0; i < 5; i++)
exec.execute(new LiftOff());
exec.shutdown();
非常常见的情况是,单个的Executor被用来创建和管理系统中所有任务。
对shutdown()方法的调用可以防止新任务被提交给这个Executor,当前线程将继续运行在shutdown被调用之前提交的所有任务。这个程序将在Executor中的所有任务完成之后尽快推出。
你可以很容易地将前面示例中的CachedThreadPool替换为不同类型的Executor。FixedThreadPool使用了有限的线程集来执行所提交的任务:
ExecutorService exec = Executors.newFixedThreadPool(5);
for(int i = 0; i < 5; i++)
exec.execute(new LiftOff());
exec.shutdown();
从任务中产生返回值
Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在java se5中引入的Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call(),中返回值,并且必须使用ExecutorService.submit()方法调用它:
class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id) {
this.id = id;
}
public String call() {
return "result of TaskWithResult " + id;
}
}
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 < 10; i++)
results.add(exec.submit(new TaskWithResult(i)));
for(Future<String> fs : results)
try {
// get() blocks until completion:
System.out.println(fs.get());
} catch(InterruptedException e) {
System.out.println(e);
return;
} catch(ExecutionException e) {
System.out.println(e);
} finally {
exec.shutdown();
} }
}
submit()方法会长生Future对象,它用Callable返回结果的特定类型进行了参数化。你可以使用isDone()方法来查询Future是否已经完成。
你可以使用Thread.currentThread()来获取当前该任务的Thread对象。
注意优先级是在run()的开头部分设定的,在构造器中设置它们不会有任何好处,因为Executor在此刻还没有开始执行任务。
虽然JDK有10个优先级,但是它与多数操作系统都不能映射得很好。唯一可一直的方法是当调整优先级的时候,只使用MAX_PRIORITY NORM_PRIORITY和MIN_PRIORITY
对于任何重要的控制或在调整应用时,都不能依赖与yield()。实际上,yield()经常被误用。
所谓后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。
for(int i = 0; i < 10; i++) {
Thread daemon = new Thread(new SimpleDaemons());
daemon.setDaemon(true); // Must call before start()
daemon.start();
}
必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程。
Daemon线程被设置成了后台模式,然后派生出许多子线程,这些线程并没有被显示地设置为后台模式,不过它们的确是后台线程。接着,Daemon线程进入了无限循环,并在循环里调用了yield()方法把控制权交给其他进程。
你应该意识到后台进程在不执行finally子句的情况下就会终止其run()方法。
当最后一个非后台线程终止时,后台线程会突然终止。
Runnable接口的名字选择很糟糕,所以我认为Task应该是好得多的名字。
加入一个线程
一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(即t.isAlive() == false)
也可以在调用join()时带上一个超时参数。
import static net.mindview.util.Print.*;
class Sleeper extends Thread {
private int duration;
public Sleeper(String name, int sleepTime) {
super(name);
duration = sleepTime;
start();
}
public void run() {
try {
sleep(duration);
} catch(InterruptedException e) {
print(getName() + " was interrupted. " +
"isInterrupted(): " + isInterrupted());
return;
}
print(getName() + " has awakened");
}
}
class Joiner extends Thread {
private Sleeper sleeper;
public Joiner(String name, Sleeper sleeper) {
super(name);
this.sleeper = sleeper;
start();
}
public void run() {
try {
sleeper.join();
} catch(InterruptedException e) {
print("Interrupted");
}
print(getName() + " join completed");
}
}
public class Joining {
public static void main(String[] args) {
Sleeper
sleepy = new Sleeper("Sleepy", 1500),
grumpy = new Sleeper("Grumpy", 1500);
Joiner
dopey = new Joiner("Dopey", sleepy),
doc = new Joiner("Doc", grumpy);
grumpy.interrupt();
}
} /* Output:
Grumpy was interrupted. isInterrupted(): false
Doc join completed
Sleepy has awakened
Dopey join completed
joiner线程将通过在Sleeper对象上调用join()方法来等待Sleeper醒来。在main()里面,每个Sleeper都有一个Joiner,这可以在输出中发现,如果Sleeper被中断或者是正常结束,Joiner将和Sleeper一同结束。
注意,Java SE5的java.util.concurrent类库包含诸如CyclicBarrier这样的工具,它们可能比最初的线程类库中的join()更加合适。
继续错误的代价由别人来承担,而承认错误的代价由自己来承担。
所有对象都自动含有单一的锁,当在对象上调用其任意synchronized方法的时候,此对象都被加锁,这时候对象上的其他synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。其所有synchronized方法共享同一个锁。