Java Thread&Concurrency(7): 深入理解Callable/Future(FutureTask)接口及其实现

背景(注释):

使用Callable可以定义一个返回结果的任务(过程中可能抛出异常)。

实现它只需要返回一个结果,不提供参数。

这个Callabel接口类似于Runnable,两者都是被设计用于被其他线程执行。区别是Runnable无法返回结果以及不能抛出受检查异常。

Executors中包含一共公共方法用于转化其他的实例为Callable实例。

public interface Callable<V> {
    V call() throws Exception;
}

通过Future可以得到异步计算的结果。这个接口支持检查任务是否执行完成,等待任务完成,以及获取计算完成之后的结果。计算结果只能通过get方法来获取,当任务未完成时会阻塞。通过cancel方法实现取消机制。附加的方法被提供能够用于确定任务已经正式完成或者被取消。当一个计算任务完成,任务的状态就不能变为取消。如果你使用Future的目的仅仅只是为了得到取消的能力,那么你可以声明Future<?>以及通过返回null作为一个结果。

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通过与ExecutorService、Callable配合使用,从而得到计算结果。

interface ArchiveSearcher { String search(String target); }
  class App {
    ExecutorService executor = ...
    ArchiveSearcher searcher = ...
    void showSearch(final String target)
        throws InterruptedException {
      Future<String> future
        = executor.submit(new Callable<String>() {
          public String call() {
              return searcher.search(target);
          }});
      displayOtherThings(); // do other things while searching
      try {
        displayText(future.get()); // use future
      } catch (ExecutionException ex) { cleanup(); return; }
    }
  }

FutureTask实现了Future以及Runnable接口,所以能够被Executor执行,比如上面的例子可以用如下的例子代替:

FutureTask<String> future =
    new FutureTask<String>(new Callable<String>() {
      public String call() {
        return searcher.search(target);
    }});
  executor.execute(future);


下面我们再来介绍JUC中实现了Future/Runnable的一个类:FutureTask。

首先FutureTask,作为一个可取消的异步计算任务。这个类提供了一个Future的基本实现,以及开始计算和取消计算的方法,查询计算是否完成,以及索取计算结果的方法。结果仅在计算完成之后才能获取。get方法将会在计算结果还没时阻塞。计算完成之后,这个计算就不能重新开启或者取消了(除非通过runAndRest方法)。

FutureTask能够用于包装Callable或者Runnable。因为FutureTask实现了Runnable,一个FutureTask能够被提交到Executor去执行(事实上也是这么构造的)。这个类还提供了受保护的方法能够用于定制自己的策略。


实现:

首先我们来看实际执行任务的run方法:

    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            runner = null;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

详情如下:

  • 首先检查state假如不为NEW或者没有设置当前线程为执行线程,则任务已经被执行过或者被其他线程执行中,返回。
  • 取得调用任务Callable,那么设置执行任务,并且用ran记录任务状态,假如有异常则执行setException。
  • 否则ran为true,任务执行完成则调用set方法。
  • 最后runnner置为null,假如此刻状态>=INTERRUPTING,则退让等待状态变为INTERRUPTED。
再看setException和set方法:
    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

 

   protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }
可以看出来他们之间的区别仅在于EXCEPTIONAL和NORMAL,这里EXCEPTIONAL为异常状态,NORMAL表明任务已经正式完成。

这个还有个COMPLETING中间状态(这个是事实上的决定任务结果的CAS操作,它后面的put操作是在结果已定的情况下执行,所以不需要CAS,并且这一步的作用是happens-before,传递结果值outcome)。


我们再来看get操作:

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        return report(s);
    }
分别调用await的限时和非限时版本,最后返回的值<=COMPLETING则为超时状态。

    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            else if (q == null)
                q = new WaitNode();
            else if (!queued)
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            else
                LockSupport.park(this);
        }
    }

详情如下:

  • 与所有其他的阻塞策略相比,这里较简单,探测的条件为s>COMPLETING。
  • 首先探测线程是否中断,如果中断则尝试从当前栈中删除这个等待者,并抛出异常InterruptedException。
  • 在状态为COMPLETING时采取退让策略yield。
  • 否则构造wait节点,进入LIFO队列,然后再限时或者非限时阻塞。
我们再来看set和setException中的finishCompletion方法,它实际上是和等待线程的协作:
    private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }

        done();

        callable = null;        // to reduce footprint
    }
这个方法是在state改变之后调用的,采用的方式是不断探测waiters(FIFO队列),当它不为空时unpark队列中的每一个线程。所以等待的线程保证可以被唤醒(在完成或者异常的情况下)。done方法可以用于定制。

我们最后来看取消方法:
    public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }
mayInterruptIfRunning用于指示是否强制取消。
详情如下:
  • 首先判断当前状态state,假如已经改变或者CAS操作在此失败,那么久返回false说明取消失败(原因是任务执行完成或者任务异常)。
  • 成功之后假如参数为true,则试着中断当前的执行任务,并且将状态最终改变为INTERRUPTED。
  • 最后同样调用finishCompletion并返回true。





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
我想将frontend 也是用volumes,将其映射到/app/frontend目录,在/app/frontend下install以及build,如何实现 docker-compose.yml文件: version: '3' services: frontend: build: context: ./frontend dockerfile: Dockerfile ports: - 8010:80 restart: always backend: build: context: ./backend dockerfile: Dockerfile volumes: - /app/backend:/app environment: - CELERY_BROKER_URL=redis://redis:6379/0 command: python manage.py runserver 0.0.0.0:8000 ports: - 8011:8000 restart: always celery-worker: build: context: ./backend dockerfile: Dockerfile volumes: - /app/backend:/app environment: - CELERY_BROKER_URL=redis://redis:6379/0 command: celery -A server worker -l info --pool=solo --concurrency=1 depends_on: - redis - backend restart: always celery-beat: build: context: ./backend dockerfile: Dockerfile volumes: - /app/backend:/app environment: - CELERY_BROKER_URL=redis://redis:6379/0 command: celery -A server beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler depends_on: - redis - backend restart: always redis: image: redis:latest ports: - 6379:6379 restart: always mysql: image: mysql:latest environment: - MYSQL_ROOT_PASSWORD=sacfxSql258147@ ports: - 8016:3306 volumes: - ./mysql:/var/lib/mysql restart: always frontend:dockerfile文件 FROM node:16.18.1 WORKDIR /app/frontend COPY package*.json ./ RUN npm install COPY . . RUN npm run build:prod FROM nginx:latest COPY --from=0 /app/frontend/dist/ /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
最新发布
07-14

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值