1)定义任务
线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供(有的地方比如Swing2中经常将其叫做可运行对象)。要想定义任务,只需要实现Runnable接口并编写run()方法,使得该任务执行命令。 但是此时呢,这个run()方法跟一个普通方法一样,并没有什么特殊的地方,不会产生任何内在的线程能力。要实现线程行为,我们必须在使用的时候显示的将一个任务附着到现场上。
2)Thread类
要想Runnable对象要想具有线程行为,传统的方式就是把它提交给一个Thread构造器。然后调用Thread类的start()方法为该线程执行必须的初始化操作,继而调用Runnable的run()方法。
下面的代码简单演示Thread的用法
public class LiftOff implements Runnable { public int countDown =10; private static int taskCount = 0; private final int id = taskCount++; public LiftOff() { System.out.println("默认构造函数"); } public LiftOff(int countDown) { System.out.println("非默认的构造函数"); this.countDown = countDown; } public String getStatus() { return "#" + id + "(" + (countDown > 0 ? countDown : "LiftOff") + ")."; } @Override public void run() { while(countDown-->0) { System.out.print(getStatus()); Thread.yield(); } } }
import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadTestWithExecutor { private static final int count = 5; /** * @param args */ public static void main(String[] args) { //1.什么都不加。 for(int i=0;i<count;i++) { Thread t = new Thread(new LiftOff()); t.start(); } System.out.println("执行完毕!"); } }
第一次执行结果
默认构造函数 默认构造函数 默认构造函数 默认构造函数 默认构造函数 执行完毕! #0(9).#0(8).#0(7).#1(9).#3(9).#2(9).#4(9).#0(6).#2(8).#4(8).#0(5).#2(7).#4(7).#0(4).#2(6).#4(6).#3(8).#2(5).#4(5).#2(4).#2(3).#4(4).#1(8).#3(7).#2(2).#1(7).#3(6).#2(1).#1(6).#3(5).#0(3).#2(LiftOff).#1(5).#3(4).#4(3).#1(4).#3(3).#0(2).#4(2).#1(3).#3(2).#0(1).#3(1).#1(2).#4(1).#1(1).#3(LiftOff).#0(LiftOff).#1(LiftOff).#4(LiftOff)
第二次执行结果
默认构造函数 默认构造函数 默认构造函数 默认构造函数 默认构造函数 执行完毕! #1(9).#3(9).#1(8).#3(8).#1(7).#3(7).#1(6).#3(6).#1(5).#3(5).#1(4).#3(4).#1(3).#3(3).#1(2).#3(2).#1(1).#3(1).#1(LiftOff).#3(LiftOff).#0(9).#2(9).#4(9).#0(8).#2(8).#4(8).#0(7).#2(7).#4(7).#0(6).#2(6).#4(6).#0(5).#2(5).#4(5).#0(4).#2(4).#4(4).#0(3).#2(3).#4(3).#0(2).#2(2).#4(2).#0(1).#2(1).#4(1).#0(LiftOff).#2(LiftOff).#4(LiftOff).
从上述两次的执行结果中,我们可以得到以下结论:
(1)多线程的执行不是按照顺序执行的,各个线程之间相互交叉。
#0(9).#0(8).#0(7).#1(9).#3(9).#2(9) 说明可Runnable0还没执行完,Runnable2 就执行了。
(2)start()方法看起来是产生了一个对长期运行方法的调用(即run方法),但是从实际的输出情况来看,start()方法迅速的返回了,因为“执行完毕”在run()方法执行完毕之前就已经输出了。实际上,我们产生的是对Runnable.run()方法的调用,并且这个方法还没有完成,但是因为这个方法是由不同的线程执行的,因此我们仍然可以执行main()线程中的其他操作(当然这种能力并不局限于main()线程,任何线程都可以启动另一个线程)。因此这时候程序会同时运行两个方法、
(3)比较两次执行结果可以发现,两次执行的结果不一样。这是因为线程调度机制是非确定的。
3)使用Exexutor
在Java SE5的java.util.concurrent包中的执行器(Executor)将为我们管理Thread对象,从而简化了并发编程。
下面的代码就列举了几种不同Executor的执行情况:
public class LiftOff implements Runnable { public int countDown =10; private static int taskCount = 0; private final int id = taskCount++; public LiftOff() { System.out.println("默认构造函数"); } public LiftOff(int countDown) { System.out.println("非默认的构造函数"); this.countDown = countDown; } public String getStatus() { return "#" + Thread.currentThread().getId() + "(" + (countDown > 0 ? countDown : "LiftOff") + ")."; } @Override public void run() { while(countDown-->0) { System.out.print(getStatus()); Thread.yield(); } } }
import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /**
* CachedThreadPool
*/ public class ThreadTestWithExecutor { private static final int count = 5; /** * @param args */ public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); for(int i=0;i<count;i++) { exec.execute(new LiftOff()); } exec.shutdown();
//3.使用FixThreadPool
ExecutorService exec2 = Executors.newFixedThreadPool(3);
for(int i=0;i<count;i++) {
exec2.execute(new LiftOff());
}
exec2.shutdown();
//4.SingleThreadExecutor
ExecutorService exec3 = Executors.newSingleThreadExecutor();
for(int i=0;i<count;i++) {
exec3.execute(new LiftOff());
}
exec.shutdown();
System.out.println("执行完毕!"); } }
这几段代码,都很简单,但是有几点需要说明
1)shutdown()方法的调用可以防止新任务被提交给这个Executor,当前线程(在上述代码中就是驱动main()的线程)将继续运行在shutdown()被调用之前提交的所有任务。
换句说,虽然是shutdown了,但是当前在执行器中的线程仍然会继续执行。
2)FixedThreadPool可以一次性预先执行代价高昂的线程分配(因而就可以限制线程的数量了)。这样会节省时间,因为你不用为每个任务都固定的付出创建线程的时间(记住,虽然每次new了一个Runnable对象,但是并不是每个Runnable对象都会new一次Thread.),直接从线程池中取线程即可。 但是,我们一定要记住一点,在任何线程池中,现有线程在可能的情况下, 都会被复用。
3)SingleThreadExecutor就像是线程数量为1的FixedThreadPool。这时候如果向其提交了多个任务,这些任务就会排队。每个任务都会在下一个任务开始之前运行结束。所有的任务将使用相同的线程。