美文网首页
Java线程学习笔记(3)

Java线程学习笔记(3)

作者: 哦呵呵_3579 | 来源:发表于2018-04-22 21:35 被阅读0次

    之前提到Executor和Future这两个接口,先说Future接口。
    Future接口的注释中描述该接口为一个异步操作的返回结果,这区别于Runnable接口没有返回值,只是单纯的run。Future接口提供了get方法来获取操作结果,但是Future接口并没有提供类似与Runnable接口的run方法,所以并不能在线程中被执行调用,因此又衍生出一个接口RunnableFuture

    public interface RunnableFuture<V> extends Runnable, Future<V> {
        void run();
    }
    

    RunnableFuture接口继承通过接口的多重继承,既有了run方法来使得自己能被线程调用,同时还拥有了get方法来获取线程的执行结果。但是自己要实现这个接口就意味着要写很多的代码,所以Java也提供了一个简单的实现类FutureTask。FutureTask这个类拥有两个构造方法

    public FutureTask(Callable<V> callable){}
    public FutureTask(Runnable runnable, V result){}
    

    这时候又出现了一个新的类型,Callable接口类似于Runnable接口,可以被线程调用并且返回一个操作结果。Callable接口只有一个方法call()会返回一个操作结果或是一个异常。

    public 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;
    }
    

    我们接着看FutureTask的两个构造函数,虽然他们的参数不一样,可是做的事情其实是一样的,public FutureTask(Runnable runnable, V result)这个构造函数中其实还是把Runnable类型的参数转换成了Callable类型

    public FutureTask(Runnable runnable, V result) {
            this.callable = Executors.callable(runnable, result);
            this.state = NEW; 
        }
    

    然后这个转换其实就是个适配器将类型进行转换,这个适配器是一个FutureTask的嵌套静态类RunnableAdapter,并且用Callable的call方法来代理Runnable的run方法

    static final class RunnableAdapter<T> implements Callable<T> {
            final Runnable task;
            final T result;
            RunnableAdapter(Runnable task, T result) {
                this.task = task;
                this.result = result;
            }
            public T call() {
                task.run();
                return result;
            }
        }
    

    当FutureTask被一个线程(Another Thread)调用的时候便开始执行run方法,run方法再调用Callable的call方法,在Another Thread线程中是这是同步的执行的,当执行完call方法之后便会修改FutureTask的state变量(state代表该异步任务的状态),并且将得到的结果存入outcome。主线程中调用get方法其实就是很简单的通过for (;;)这样简单的循环查询state状态,当状态为complete的时候就获取outcome的值得到异步的操作结果,这就完成了一个简单的异步操作。其实FutureTask就是一个异步的任务,它定义了任务被线程调用的时候执行的操作以及任务的运行状态和返回结果,但想要异步执行就需要一个线程执行器来执行该任务,不然他就是一个普通的类,与我们平常写的class并没有任何区别。例如下面的代码:

    class callabelTest:Callable<String>{
        override fun call(): String {
            for (i in 0..1000000){
                println(i)
            }
            return "finish"
        }
    }
    
    var task = FutureTask(callabelTest())
        task.run()
        task.get()
    

    而当我们通过线程来调用的时候就不一样了,如下代码,这时候会执行到get方法,然后阻塞等待task的run方法执行完:

    var task = FutureTask(callabelTest())
        Thread(task).start()
        task.get()
    

    接下来我们讲Executor几口,之前讲的Thread类是Java提供的实现类,不需要我们再多写什么代码,而只要一个Runnable类型的参数作为构造函数的参数就可以直接运行了。而Executor接口则是需要我们自己写一个线程的实现类,例如上面聚德例子,但是默认提供的实现类只是单纯的从当前处理器可用的线程中取出一个来用,无法管理,所以就衍生出Executor接口,让我们自己来实现线程。但是自己写一个实现类很麻烦,所以Java提供了一个工厂类Executors来快速创建实现了Executor接口的线程执行器。Executors可以创建三种类型的线程执行器,分别是ExecutorService、ScheduledExecutorService和ThreadFactory类型。

    先来说ExecutorService,ExecutorService继承了Executor接口。并扩展了Executor接口的外部视图,使其支持shutdown操作,并且支持submit和invokeAll方法来向执行器注入需要被线程执行的任务,并且返回n个Future对象来让客户检测对应Task的运行状态。Java还提供了ExecutorService接口的默认抽象类AbstractExecutorService,该抽象类实现了submit方法和invokeAll方法,但这就有一个问题,AbstractExecutorService抽象类提供的invokeAll虽然可以一次执行多个Task,但实际还是通过迭代器一个一个来执行的,说白了还是一个线程来执行所有的Task,只不过器支持查询所有Task的状态以及关闭线程,因此Java有提供了一个实现类ThreadPoolExecutor,这个类继承自AbstractExecutorService抽象类,其构造方法中支持定义该实例对象所占有的线程池的大小,并且支持一个Task队列,这就意味着我们可以自定义一个线程池,同时处理n个任务,具体一次可以同时执行多少个Task就由线程池的大小决定,避免了抢占其他的线程,实现对并发数的控制,当Task过多的情况下就需要进行排队,只有当线程池中有空余线程的时候才会再拿取Task进行处理。
    ScheduledExecutorService接口继承自ExecutorService,支持了Task执行时可以将其延时执行或者按阶段延时的执行,其他的与ExecutorService接口基本相同,其实现类ScheduledThreadPoolExecutor,继承了ThreadPoolExecutor类以及ScheduledExecutorService接口,实现了对Task执行过程的更进一步的控制。
    ThreadFactory则为提供给我们自定义的工厂类,只要实现newThread方法即可,并且返回的类型为Thread类型,这意味着我们可以把Thread(Runnable)这样的代码隐藏到内部来实现,例如:

    class SimpleThreadFactory:ThreadFactory{
        override fun newThread(r: Runnable?): Thread {
            return Thread(r)
        }
    }
    

    java.util.concurrent包中的常用组件基本如上面提到的这些,后面还有其他的另行补充,本人能力有限,有些地方可能描述不正确,欢迎大家指正,谢谢。

    相关文章

      网友评论

          本文标题:Java线程学习笔记(3)

          本文链接:https://www.haomeiwen.com/subject/vezzkftx.html