(并发)线程的创建

来源:互联网 发布:水中刀 知乎 编辑:程序博客网 时间:2024/06/18 15:23

定义任务

要想定义任务,只需要实现Runnable接口。
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.print(status());            Thread.yield();        }    }    public static void main(String[] args) {        new LiftOff().run();    }}
标识符id可以用来去人任务的多个实例,它是final的,因为它在被创建的时候就不希望被修改。在run()方法中,总是会以某种形式的循环来进行,使得任务一直运行下去知道不再需要。通常run()是会被写成无限循环的,因此我们就不得不考虑如何安全的终止线程。yield()是对线程的调度器。它声明:我已执行完声明周期中最重要的部分,可以切换给其他任务。当然这完全是选择性了,如果开启几个任务,我们可能会看见任务在切换进行。

Thread类

将Runnable对象转变为工作任务的传统方式是把它提交给Thread构造器。
public class BasicThreads {    public static void main(String[] args) {        Thread t = new Thread(new LiftOff());        t.start();        System.out.println("Waiting for LiftOff");    }}//outputWaiting for LiftOff#0(9),#0(8),#0(7),#0(6),#0(5),#0(4),#0(3),#0(2),#0(1),#0(liftoff!),
Thread构造器只需要一个Runnable对象。调用Thread对象的start()方法为该线程执行必须的初始化操作,然后调用Runnable的run()方法,以便在这个新线程中启动该任务。可以看到Waiting for LiftOff在LiftOff.run()之前就已经输出,则表示该句输出是main()线程和LiftOff.run()在执行。这并不表示只有main()线程才能这样,任何线程都能启动另一个线程。接下来我们看看更多任务被更多的线程驱动:
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");    }}//out#0(9),#0(8),#2(9),#1(9),#4(9),#2(8),#2(7),#2(6),#0(7),Waiting for LiftOff#3(9),#0(6),#2(5),#4(8),#1(8),#2(4),#4(7),#1(7),#1(6),#0(5),#3(8),#3(7),#3(6),#3(5),#3(4),#3(3),#3(2),#0(4),#1(5),#1(4),#4(6),#2(3),#4(5),#1(3),#4(4),#0(3),#3(1),#0(2),#0(1),#0(liftoff!),#4(3),#4(2),#4(1),#4(liftoff!),#1(2),#1(1),#1(liftoff!),#2(2),#2(1),#2(liftoff!),#3(liftoff!),
从输出可以看出不同的任务的执行在线程被换进换出时混在一起了。这种交换是线程调度器自行控制的。在使用普通对象时对GC来说是公平的,但是在使用Thread时,情况就不同了。在其每个Thread任务退出其run()没有死亡之前,GC无法清楚它。可以看出这些任务确定都是执行了整个流程,因此,**一个线程会创建一个单独的执行线程,在对start()调用完成之后,它仍旧会继续存在。**

使用Executor

Java SE5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。Executor在客户端和任务执行端提供了一个间接层;与客户端直接执行任务不同,这个终结对象将执行任务。Executor允许你管理异步任务的执行,而无须显式地管理线程的声明周期。
public class CacheThreadPool {    public static void main(String[] args) {        ExecutorService exec = Executors.newCachedThreadPool();        for (int i = 0; i < 5; i++) {            exec.execute(new LiftOff());        }        exec.shutdown();    }}//out#1(9),#0(9),#4(9),#2(9),#2(8),#2(7),#2(6),#3(9),#2(5),#3(8),#4(8),#0(8),#1(8),#0(7),#1(7),#4(7),#1(6),#1(5),#3(7),#3(6),#2(4),#3(5),#1(4),#4(6),#0(6),#4(5),#1(3),#3(4),#2(3),#3(3),#3(2),#1(2),#1(1),#4(4),#0(5),#0(4),#0(3),#4(3),#4(2),#1(liftoff!),#3(1),#3(liftoff!),#2(2),#2(1),#2(liftoff!),#4(1),#4(liftoff!),#0(2),#0(1),#0(liftoff!),
ExecutorService是具有服务周期的Exrcutor,例如关闭。它知道如何构建恰当的上下文来执行Runnable对象。ExecutorSerivce对象使用的是静态的Executor方法创建的,这个方法可以确定其Executor类型;非常常见的情况是,单个Executor被用来创建和管理系统中所有的任务。对shutdown()方法的调用可以防止新任务被提交给这个Executor,当前线程(在上述例子就是main()的线程)将继续运行在shutdown()被调用之前提交的所有任务。也就是这个main()只会执行上面循环中的任务。在执行完成就会尽快退出。

你可以很容易将示例中的CachedThreadPool替换为其他类型。
public static void main(String[] args) {        ExecutorService exec = Executors.newFixedThreadPool(5);        for (int i = 0; i < 5; i++) {            exec.execute(new LiftOff());        }        exec.shutdown();    }//out#4(4),#1(4),#4(3),#0(4),#2(4),#3(4),#2(3),#0(3),#4(2),#1(3),#4(1),#0(2),#2(2),#3(3),#2(1),#0(1),#4(liftoff!),#1(2),#0(liftoff!),#2(liftoff!),#3(2),#1(1),#3(1),#1(liftoff!),#3(liftoff!),//当启动线程是1的情况#0(4),#0(3),#0(2),#0(1),#0(liftoff!),#1(4),#1(3),#1(2),#1(1),#1(liftoff!),#2(4),#2(3),#2(2),#2(1),#2(liftoff!),#3(4),#3(3),#3(2),#3(1),#3(liftoff!),#4(4),#4(3),#4(2),#4(1),#4(liftoff!),#5(4),#5(3),#5(2),#5(1),#5(liftoff!),
FixedThreadPool固定线程数量。它的好处就是,可以节省创建线程的开销,因为它是一次性的预先执行代价高昂的线程分配。

Executor类型说明

CachedThreadPool是我们会主要使用的类型,它是在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选。只有在这种方式会引发问题时,你才需要切换到FixedThreadPool。SingleThreadExecutor就像是线程数量为1的FixedThreadPool。这对于希望某个任务长期连续的运行很有用。如果对SingleThreadExecutor提交了多个任务,那么这些任务将会排队执行。因此,SingleThreadExecutor会序列化所有提交给它的任务,并会维护它自己的悬挂任务队列。

从任务中产生返回值

public class TaskWithResult implements Callable<String>{    private int id;    public TaskWithResult(int id){        this.id = id;    }    public String call() throws Exception {        return "result of TackWithResult " + id;    }}public class CallableDemo{    public static void main(String[] args) {        ExecutorService exec = Executors.newCachedThreadPool();        ArrayList<Future<String>> results = new ArrayList<>();        for (int i = 0; i < 10; i++) {            results.add(exec.submit(new TaskWithResult(i)));        }        for (Future<String> future : results) {            try {                System.out.println(future.get());            } catch (InterruptedException e) {                System.out.println(e);                return;            } catch (ExecutionException e) {                System.out.println(e);            } finally{                exec.shutdown();            }        }    }}//outresult of TackWithResult 0result of TackWithResult 1result of TackWithResult 2result of TackWithResult 3result of TackWithResult 4result of TackWithResult 5result of TackWithResult 6result of TackWithResult 7result of TackWithResult 8result of TackWithResult 9
submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化。你可以用isDone()方法来查询Future是否已经完成。
原创粉丝点击