Java并发:多线程

来源:互联网 发布:python 抢购脚本 编辑:程序博客网 时间:2024/06/04 00:56

相关文章:
1.原子类 ,锁
http://blog.csdn.net/youyou1543724847/article/details/52735510

2.多线程相关的

3.线程安全的集合
http://blog.csdn.net/youyou1543724847/article/details/52734876

4.ThreadLocal
http://blog.csdn.net/youyou1543724847/article/details/52460852

5.并发:其他(如volatile,原子类,不变类)
http://blog.csdn.net/youyou1543724847/article/details/52735829

线程的生成的方法

主要有:
1.Runable,Callable,Future,FutureTask
2.Thread
3.线程池

1.Runnable,Callable,Future,FutureTask相关知识

Runnable,Callable的区别:
Runnable:位于java.lang包中。由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。
Callable:位于java.util.concurrent包下,实现的是call方法,且call方法可待返回值,可抛出异常

Runnable

@FunctionalInterfacepublic interface Runnable {    /**     * When an object implementing interface <code>Runnable</code> is used     * to create a thread, starting the thread causes the object's     * <code>run</code> method to be called in that separately executing     * thread.     * <p>     * The general contract of the method <code>run</code> is that it may     * take any action whatsoever.     *     * @see     java.lang.Thread#run()     */    public abstract void run();}

Callable

@FunctionalInterfacepublic interface Callable<V> {    /**     * Computes a result, or throws an exception if unable to do so.     *     * @return computed result     * @throws Exception if unable to compute a result     */    V call() throws Exception;}

使用
Runable比较常见,这里就不讨论了,主要看看Callable,FutureTask的用法
那么怎么使用Callable呢?一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:

<T> Future<T> submit(Callable<T> task);<T> Future<T> submit(Runnable task, T result);Future<?> submit(Runnable task);

第一个submit方法里面的参数类型就是Callable。

暂时只需要知道Callable一般是和ExecutorService配合来使用的,具体的使用方法讲在后面讲述。

一般情况下我们使用第一个submit方法和第三个submit方法,第二个submit方法很少使用。

Future

public interface Future<V> {   boolean cancel(boolean mayInterruptIfRunning);    boolean isCancelled();    boolean isDone();    V get() throws InterruptedException, ExecutionException;    V get(long timeout, TimeUnit unit)        throws InterruptedException, ExecutionException, TimeoutException;}

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

在Future接口中声明了5个方法,下面依次解释每个方法的作用:
•cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
•isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
•isDone方法表示任务是否已经完成,若任务完成,则返回true;
•get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
•get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

  也就是说Future提供了三种功能:
  1)判断任务是否完成;
  2)能够中断任务;
  3)能够获取任务执行结果。
  因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。
  

FutureTask

我们先来看一下FutureTask的实现:

public class FutureTask<V> implements RunnableFuture<V> { /** The underlying callable; nulled out after running */    private Callable<V> callable;    /** The result to return or exception to throw from get() */    private Object outcome; // non-volatile, protected by state reads/writes    /** The thread running the callable; CASed during run() */    private volatile Thread runner;    /** Treiber stack of waiting threads */    private volatile WaitNode waiters;}public interface RunnableFuture<V> extends Runnable, Future<V> {    /**     * Sets this Future to the result of its computation     * unless it has been cancelled.     */    void run();}

可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
  FutureTask提供了2个构造器:

    public FutureTask(Callable<V> callable) {        if (callable == null)            throw new NullPointerException();        this.callable = callable;        this.state = NEW;       // ensure visibility of callable    }    public FutureTask(Runnable runnable, V result) {        this.callable = Executors.callable(runnable, result);        this.state = NEW;       // ensure visibility of callable    }

事实上,FutureTask是Future接口的一个唯一实现类。
使用示例
这里写图片描述
这里写图片描述

 2.使用Callable+FutureTask获取执行结果
这里写图片描述

Thread相关知识

Thread类

java虚拟机JVM持续执行所有线程,直到以下任何一个发生:
a.Runtime类的exit方法被调用,并且安全管理器允许退出操作的执行。
b.所有的非守护线程都已经停止运行,无论是在run方法中调用返回,还是在run方法之上抛出了一个异常。
守护线程的优先级较低,最常见的守护线程为gc线程。

关于Thread线程的停止:

tread.stop方法已经停用了,因为会导致不一致、不安全的的状态。oracle推荐程序员自己控制线程的停止。一般来说,线程中保存一个变量,线程运行时,每隔一段时间,就去检查该变量,然后确定是否要停止了。

方法:

  1. static sleep(long mills)方法
    这个方法其实不是java自己的方法,是native方法。当此方法被调用以后,线程会被暂停,时间长短由参数决定。但是该方法被执行的时候,并不会释放任何监视器的所有权(即不会释放已经获得的锁)。
    2.构造方法
    Thread(),这个构造方法的内部实现其实就是调用了init()方法,为其分配了一个”Thread-“+数字的名字,数字从0开始。
    Thread(Runnable target),和上面不同的是,这个指定了一个Runnable接口的参数,实现了这个接口的类的run方法将被执行。
    Thread(String name),这个和第一个只有一点不同,就是重新指定了线程的名字。
    其实无论是哪一种构造方法,都是同样的内部实现,调用init方法,只是传入的参数不一样。
    3.start方法
    线程只能使用start方法来进行调用,一旦start方法被调用,那么这个线程就会开始运行,JVM会开始调用这个线程的run方法。
    4.run方法
    其实线程类里面的run方法的实现就是调用Runnable接口的实例中的run方法。
    这样说吧,Thread类本身就是Runnable的实例,如果是通过定义Thread类的实例,那么调用的就是Thread类中的run方法,所以通常来说必须重写Thread类中的run方法,否则就什么也不执行就直接返回。
    而如果专门写一个类实现了Runnable接口,那么这个类就要作为target传给Thread类的构造方法,调用的也是这个类中的run方法。

5.interrupt(),interrupted(),isinterrupted()方法
interrupt():通过设置isInterrupted状态,使得处于阻塞状态的线程(包括被Object.wait,Thread.join,Thread.sleep方法之一阻塞的线程)退出阻塞的状态,并且会抛出一个InterruptedException。

interrupted():测试当前线程是否被打断了,并且中断状态会被该方法清除。也就是说,如果该方法被连续调用两次,则第二次会返回false(除非在第二次调用前又被中断了)

isinterrupted():测试测试当前线程是否被打断了,不清除中断状态。

6.setPriority和getPriority方法
Priority的值是从1到10,不能越过这个界限。默认情况下priority的值为5。

7.join方法(等效于 join(0))
有几个重载方法,join(long mills),join(long mills, int nanos)。
作用是等待该线程死亡。如在线程ThreadA 中创建了线程ThreadB,然后{ThreadB.start(); ThreadB.join() }则表明ThreadA会暂停执行,知道ThreadB结束。

如果加了时间参数,那么也就是要等最多那么多时间。时间参数为0就是说,一直等下去,直到执行完毕。

可以通过interrupt方法来打断,会抛出异常

8.setDaemon(boolean on)方法
首先说说什么叫守护线程。一共2种线程,一种是用户线程,一种是守护线程。如果用户线程全部都推出了,JVM也就退出了。也就是说,守护线程的级别是很低的,它的作用就是为用户线程提供服务。其实2种线程是可以相互转换的,各个方面也基本上是相同的,唯一的不同点只有一个,就是判断JVM什么时候离开。
setDaemon需要在start方法调用之前使用
试想,本来守护线程就是为用户线程服务的,如果用户线程全部都不存在了,那么守护线程也没有存在的意义了,这个时候JVM也就可以退出了。

需要注意的是,如果你在一个守护线程里面创建了一个新的线程,那么这个线程不用设定,直接就是守护线程。对于用户线程来说,同理。

具体怎么用呢,举个例子。假如你在编辑一份文档,那么后台就可能有一个守护线程,它一直在后台检查拼写错误,并不耽误你写。等它的时间片到了,拼写错误就会被提示出来。当你退出编辑的时候,拼写检查也就没有必要了。

9.yield()
让出当前线程的处理器时间片,重新调度

线程状态

JVM主要是用C++实现的,JVM定义的Thread的类继承结构如下:

这里写图片描述

这些类构成了JVM的线程模型,其中最主要的是下面几个类:
java.lang.Thread: 这个是Java语言里的线程类,由这个Java类创建的instance都会 1:1 映射到一个操作系统的osthread

JavaThread: JVM中C++定义的类,一个JavaThread的instance代表了在JVM中的java.lang.Thread的instance, 它维护了线程的状态,并且维护一个指针指向java.lang.Thread创建的对象(oop)。它同时还维护了一个指针指向对应的OSThread,来获取底层操作系统创建的osthread的状态

OSThread: JVM中C++定义的类,代表了JVM中对底层操作系统的osthread的抽象,它维护着实际操作系统创建的线程句柄handle,可以获取底层osthread的状态

VMThread: JVM中C++定义的类,这个类和用户创建的线程无关,是JVM本身用来进行虚拟机操作的线程,比如GC。

总结:java.lang.Thread:Java中用户生成的类,在JVM中,用JavaThread来表示。同时每一个JVM中的线程用OSThread来关联一个对应的OS底层线程。

从JVM的角度来看待线程状态的状态有以下几种:

这里写图片描述

其中主要的状态是这5种:
_thread_new: 新创建的线程
_thread_in_Java: 在运行Java代码
_thread_in_vm: 在运行JVM本身的代码
_thread_in_native: 在运行native代码
_thread_blocked: 线程被阻塞了,包括等待一个锁,等待一个条件,sleep,执行一个阻塞的IO等

线程池相关知识

参考文献:
http://www.oschina.net/question/565065_86540

Executor接口

An object that executes submitted Runnable tasks
这里写图片描述
这里写图片描述

ExecutorService接口

继承了Executor接口,添加了两个方法shutdown()和shutdownnow()两个方法,并增加了Future特性
区别:

      shutdown():不运行提交新的任务,之前提交成功的任务会正常执行完       shutdownnow():新的任务无法提交成功,且之前提交的任务也会终止

这里写图片描述

ThreadPoolExecutor类

An ExecutorService that executes each submitted task using one of possibly several pooled threads, normally configured using Executors factory methods. 用来执行线程池中任务的ExecutorService
可用来配置一些特定的线程池。
使用:

        ThreadPoolExecutor executor=new ThreadPoolExecutor(2,3,100,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());

ThreadPoolExecutor详解

ThreadPoolExecutor的完整构造方法的签名是:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) .
corePoolSize - 池中所保存的线程数,包括空闲线程。
maximumPoolSize-池中允许的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。
threadFactory - 执行程序创建新线程时使用的工厂。
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

ThreadPoolExecutor是Executors类的底层实现。

在JDK帮助文档中,有如此一段话:
“强烈建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程)
queue上的三种类型。

排队有三种通用策略

直接提交:工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

    无界队列:使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。    有界队列:当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。  

BlockingQueue的选择

例子一:使用直接提交策略,也即SynchronousQueue。
首先SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。在这里不是核心线程便是新创建的线程,但是我们试想一样下,下面的场景。

    我们使用一下参数构造ThreadPoolExecutor:     new ThreadPoolExecutor(   2, 3, 30, TimeUnit.SECONDS,    new  SynchronousQueue<Runnable>(),             new RecorderThreadFactory("CookieRecorderPool"),    new ThreadPoolExecutor.CallerRunsPol());     当核心线程已经有2个正在运行.    1.此时继续来了一个任务(A),根据前面介绍的“如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。”,所以A被添加到queue中。    2.又来了一个任务(B),且核心2个线程还没有忙完,OK,接下来首先尝试1中描述,但是由于使用的SynchronousQueue,所以一定无法加入进去。    3.此时便满足了上面提到的“如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。”,所以必然会新建一个线程来运行这个任务。    4.暂时还可以,但是如果这三个任务都还没完成,连续来了两个任务,第一个添加入queue中,后一个呢?queue中无法插入,而线程数达到了maximumPoolSize,所以只好执行异常策略了。    所以在使用SynchronousQueue通常要求maximumPoolSize是无界的,这样就可以避免上述情况发生(如果希望限制就直接使用有界队列)。对于使用SynchronousQueue的作用jdk中写的很清楚:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。    什么意思?如果你的任务A1,A2有内部关联,A1需要先运行,那么先提交A1,再提交A2,当使用SynchronousQueue我们可以保证,A1必定先被执行,在A1么有被执行前,A2不可能添加入queue中。    例子二:使用无界队列策略,即LinkedBlockingQueue    如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。那么当任务继续增加,会发生什么呢?    如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。OK,此时任务变加入队列之中了,那什么时候才会添加新线程呢?    如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。这里就很有意思了,可能会出现无法加入队列吗?不像SynchronousQueue那样有其自身的特点,对于无界队列来说,总是可以加入的(资源耗尽,当然另当别论)。换句说,永远也不会触发产生新的线程!corePoolSize大小的线程数会一直运行,忙完当前的,就从队列中拿任务开始运行。所以要防止任务疯长,比如任务运行的实行比较长,而添加任务的速度远远超过处理任务的时间,而且还不断增加,不一会儿就爆了。    例子三:有界队列,使用ArrayBlockingQueue。    这个是最为复杂的使用,所以JDK不推荐使用也有些道理。与上面的相比,最大的特点便是可以防止资源耗尽的情况发生。     举例来说,请看如下构造方法:     new ThreadPoolExecutor(         2, 4, 30, TimeUnit.SECONDS,         new ArrayBlockingQueue<Runnable>(2),         new RecorderThreadFactory("CookieRecorderPool"),        new ThreadPoolExecutor.CallerRunsPolicy());  

假设,所有的任务都永远无法执行完。
对于首先来的A,B来说直接运行,接下来,如果来了C,D,他们会被放到queue中,如果接下来再来E,F,则增加线程运行E,F。但是如果再来任务,队列无法再接受了,线程数也到达最大的限制了,所以就会使用拒绝策略来处理。

keepAliveTime

jdk中的解释是:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
有点拗口,其实这个不难理解,在使用了“池”的应用中,大多都有类似的参数需要配置。比如数据库连接池,DBCP中的maxIdle,minIdle参数。
什么意思?接着上面的解释,后来向老板派来的工人始终是“借来的”,俗话说“有借就有还”,但这里的问题就是什么时候还了,如果借来的工人刚完成一个任务就还回去,后来发现任务还有,那岂不是又要去借?这一来一往,老板肯定头也大死了。
合理的策略:既然借了,那就多借一会儿。直到“某一段”时间后,发现再也用不到这些工人时,便可以还回去了。这里的某一段时间便是keepAliveTime的含义,TimeUnit为keepAliveTime值的度量。

RejectedExecutionHandler handler

当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法execute(java.lang.Runnable) 中提交的新任务将被拒绝。在以上两种情况下,execute 方法都将调用其RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四种预定义的处理程序策略:
A.在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。
B.在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
C.在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。
D.在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。

定义和使用其他种类的 RejectedExecutionHandler 类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。

其他

某个线程运行抛出异常,不会导致所有的线程都停止,就算是运行时异常。
线程池的默认的任务被拒处理的策略是: ThreadPoolExecutor.AbortPolicy

Executors工具类

一个用来创建定制线程池(Factory and utility methods for Executor, ExecutorService, ScheduledExecutorService, ThreadFactory, and Callable classes defined in this package. This class supports the following kinds of methods:
Methods that create and return an ExecutorService set up with commonly useful configuration settings.
• Methods that create and return a ScheduledExecutorService set up with commonly useful configuration settings.
• Methods that create and return a “wrapped” ExecutorService, that disables reconfiguration by making implementation-specific methods inaccessible.
• Methods that create and return a ThreadFactory that sets newly created threads to a known state.
• Methods that create and return a Callable out of other closure-like forms, so they can be used in execution methods requiring Callable.
)

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
1. newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程

  1. newCachedThreadPool
    创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
    那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

4.newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

使用方法:

   如:ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); 

关于java.util.concurrent.RejectedExecutionException

    目前看来,最主要有2种原因。    第一:            你的线程池ThreadPoolExecutor 显示的shutdown()之后,再向线程池提交任务的时候。如果你配置的拒绝策略是AbortPolicy的话,这个异常就会抛出来。    第二:            当你设置的任务缓存队列过小的时候,或者说, 你的线程池里面所有的线程都在干活(线程数== maxPoolSize),并且你的任务缓存队列也已经充满了等待的队列, 这个时候,你再向它提交任务,则会抛出这个异常。

线程池使用案例一

//连个线程,数水果篮子中的水果总数import java.util.Scanner;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Count implements Runnable{        int sum;        int map[];        public Count(int map[])        {                this.map=map;                sum=0;        }        public static void main(String args[]) throws InterruptedException        {                Scanner sc=new Scanner(System.in);                int M=sc.nextInt();                int N=sc.nextInt();                int map[][]=new int[M][N];                for(int i=0;i<M;i++)                        for(int j=0;j<N;j++)                                map[i][j]=sc.nextInt();                ExecutorService pool=Executors.newFixedThreadPool(2);                //不需要显示手动启动                Count task[]=new Count[M];                for(int i=0;i<M;i++)                {                        task[i]=new Count(map[i]);                        pool.execute(task[i]);                }                pool.shutdown();            //当所有任务执行完后,将线程池关闭                while(true)                {                        if(pool.isTerminated())                        {                                int sum=0;                                for(int i=0;i<M;i++)                                {                                        System.out.println(task[i].sum);                                        sum=sum+task[i].sum;                                }                                System.out.println(sum);                                break;                        }                        else                                Thread.sleep(100);                }        }        public void run()        {                for(int i=0;i<map.length;i++)                        sum=sum+map[i];        }}

线程池使用案例二

//连个线程,数水果篮子中的水果总数,通过FutureTaskimport java.util.Scanner;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.FutureTask;public class Test implements Callable<Integer>{        int map[];        public Test(int map[])        {                this.map=map;        }        public static void main(String args[]) throws InterruptedException, ExecutionException        {                Scanner sc=new Scanner(System.in);                int M=sc.nextInt();                int N=sc.nextInt();                int map[][]=new int[M][N];                for(int i=0;i<M;i++)                        for(int j=0;j<N;j++)                                map[i][j]=sc.nextInt();                ExecutorService pool=Executors.newFixedThreadPool(2);                //不需要显示手动启动                FutureTask<Integer> result[]=new FutureTask[M];                for(int i=0;i<M;i++)                {                        result[i]=new FutureTask<Integer>(new Test(map[i]));                    pool.execute(result[i]);                }                pool.shutdown();               //当所有任务执行完后,将线程池关闭               int sum=0;               Integer temp;               for(int i=0;i<M;i++)               {                       temp=result[i].get();                       sum=sum+ temp;               }               System.out.println(sum);        }                @Override                public Integer call() throws Exception {                        int sum=0;                        for(int i=0;i<map.length;i++)                sum=sum+map[i];                        return sum;                }}
0 0