Java多线程学习之任务的创建以及在线程中执行任务

传统的创建任务、驱动任务的方式

1.继承Thread类

  通过继承Thead类,并重写run方法,在run方法里面编码具体的任务,调用对象的start方法驱动任务。

  

public class TestThread extends Thread{

    private int count = 5;
  //创建介绍String形参的构造器,一般参数作为任务的名称,以区分不同的线程
    public TestThread(String name) {
        super(name);
    }
  //run方法体,就是要执行的任务
    public void run() {
        while (true) {
            System.out.println(this);
            if(--count == 0)
                break;
        }
    }
    //重写toString方法实现可读性更好的线程打印
    public String toString() {
        return getName()+"   count="+count;
    }

    public static void main(String[] args) {
//创建5个任务对象,并且调用start方法让线程执行任务; for(int i=0;i<5;i++) { new TestThread("#Thread "+i).start(); }
//这里往下都是当前线程(即是执行main函数这个线程),而自定义的TestThread任务都是在main线程中开启新的线程来驱动 } }
输出:

#Thread 0 count=5

#Thread 3 count=5

#Thread 1 count=5

#Thread 2 count=5

#Thread 1 count=4

#Thread 3 count=4

#Thread 3 count=3

#Thread 0 count=4

#Thread 3 count=2

#Thread 1 count=3

...

#Thread 0 count=1

#Thread 4 count=3

#Thread 4 count=2

#Thread 2 count=2

#Thread 4 count=1

#Thread 2 count=1

  从log可以看出,这些任务被调动的机会很随机,这取决于线程调度器,而调度器的调度机制又取决于运行的操作系统,即是在同一个平台上,运行多次每次都会出现不一致的结果。

2.实现Runnable接口

  这种方式与继承Thread类的方式类似,任务类实现Runnable接口的run方法,但是这个任务只是被定义了,并没有产生线程的行为,所以还必须把任务类作为创建Thread对象的构造器形参,并通过调用Thread实例的start方法,使任务附着到线程中。

public class LiftOff implements Runnable{

    private int count=4;

    private static int taskId = 0;

    private int id = taskId++;

    private String getStatus() {
        return "task"+id+"("+count+")";
    }
  //定义任务
    public void run() {
        while (count-->0) {
            System.out.println(getStatus());
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<3;i++) {
//将任务对象实例通过构造器传递给Thread实例,使任务附着到线程上得到执行 new Thread(new LiftOff()).start(); } } }
输出:

task1(3)

task2(3)

task0(3)

task2(2)

task1(2)

task2(1)

task0(2)

task2(0)

task1(1)

task0(1)

task1(0)

task0(0)

  同继承Thread类执行线程一样,任务都是被“随机”执行。不过,这种方式下,任务与线程明确分开在不同的对象,架构比前一种要清晰。

使用Executor

  从JavaSE5起,提供了执行器(Executor)来简化并发编程。使用Executor,我们不需要收到创建Thread实例,将任务交给Excutor,至于把任务附着到线程上执行,又或者管理线程的生命周期,都不需要我们处理。

public class CacheThreadPoolTest {

    public static void main(String[] args) {
//通过静态方法创建newCacheThreadPool的Exector,可以创建FixedThreadPool、SingleThreadExecutor等类型的Executor ExecutorService exec = Executors.newCachedThreadPool(); for(int i=0;i<5;i++) {
//将任务加到Executor上 exec.execute(new LiftOff()); }
//开始顺序关闭Executor开启的线程,之后不能再添加任务到Executor,任务执行完后相应的线程将被关闭 exec.shutdown(); } }

  Executor很好地分离的任务及驱动任务的线程角色,客户端只需要专注于任务定义,而线程创建管理则交给Executor来负责。下面看看几种不同的Executor的特点:

CachedThreadPool:Executor首选的线程池,通常会创建与所需数量相同的线程,在回收旧线程时停止创建新线程;
FixedThreadPool:一次性预先创建固定数量的线程,可以节省后期线程创建的开销,并且防止滥用线程;
SingleThreadExecutor:类似线程数量为1的FixedThreadPool,适用于所以任务都要在同一个线程中执行的情景(执行次序为任务被提交的先后顺序),还可以用于执行短任务;
public class LiftOff implements Runnable{

    private int count=4;

    private static int taskId = 0;

    private int id = taskId++;

    private String getStatus() {
        return "task"+id+"("+count+")";
    }

    public void run() {
        try {
            while (count-->0) {
                System.out.println(getStatus());
                Thread.sleep(2000);
            }
        }catch (Exception e) {
            e.printStackTrace(System.out);
        }
    }

}
public class SingleThreadExecutorTest {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        for(int i=0;i<3;i++) {
            executor.execute(new LiftOff());
        }
//这里一定要关闭线程,否则主线程将一直阻塞
executor.shutdown(); } }
输出:

task0(3)

task0(2)

task0(1)

task0(0)

task1(3)

task1(2)

task1(1)

task1(0)

task2(3)

task2(2)

task2(1)

task2(0)

从任务中产生返回值

  无论是实现Runnable接口还是继承Thread类,都只是单独执行任务,没能返回值。要想从任务中产生返回值,需要使用Callable接口,其中重写call方法并得到参数化的Future类型的返回值。实现Callable接口的任务需要Executor使用submit来提交。

//定义任务,实现Callable,返回值类型为String
public class TaskWithResult implements Callable<String>{ private static int taskId = 0; private int id = taskId++; public String toString() { return "#task"+id; } public String call() throws Exception { return this.toString(); } }
public class CallableTest {

    public static void main(String[] args) throws Exception{
        ExecutorService executor = Executors.newSingleThreadExecutor();
        List<Future<String>> futureList = new ArrayList<Future<String>>();
        for(int i=0;i<3;i++) {
//通过executor的submit来提交,submit返回参数化的Future类型 futureList.add(executor.submit(new TaskWithResult())); } executor.shutdown(); for(Future<String> future: futureList) {
//Future的get可以得到任务的真正返回值 System.out.println(future.get()); } } }
输出:

#task0

#task1

#task2

  需要注意的是,从Future中得到返回值时使用了get,但是get会引起阻塞直到任务完成并返回,为了避免阻塞,可以用isDone方法来查询Future是否完成。