Java-使用Callable实现多线程的之JDK源码简单学习实践


当提起Java实现多线程时候,我们一般来说是继承Thread类或者实现Runnable接口两种。如果不是很严格的说,还有一种就是使用JDK的java.util.concurrent.Callable实现多线程,这需要结合Thread或者线程池实现。然而昨天参加某某公司电面时候被问到,由于之前简单使用过,没有仔细学习过导致面试回答语无伦次。下面就先简单分析一下实现原理和一个自己非常简陋的实现。


首先我们先回顾一下使用Callable实现多线程的步骤:

1:自定义类实现Callble接口,在Callable的call方法中实现多线程要完成的任务。那么问题来了,Callable是什么呢?Callable其实类似于Runnable接口一样,其实例可以被线程执行,具体执行实现是在Runnable中调用了Callable接口的call方法。例如:当我们使用Runnable实现多线程时候,也是自定义类实现Runnable接口之后,创建其实例给Thread构造器,然后启动,代码如下:

public class RunnableDemo implements Runnable {

	@Override
	public void run() {
		System.out.println("RunnableDemo....");
		
	}
	
	public static void main(String[] args) {
		
		
		RunnableDemo runnable=new RunnableDemo();
		
		new Thread(runnable).start();
		
	}

}

那么我们在看一下Callble实现代码:

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableDemo implements Callable {

	@Override
	public Integer call() throws Exception {

		int sum = 0;
		for (int i = 0; i <= 99; i++) {
			sum += i;
		}
		return sum;
	}
	
	public static void main(String[] args) {
		
		
		CallableDemo call=new CallableDemo();
		
		FutureTask ft =new FutureTask<>(call);
		
		new Thread(ft).start();
		
		
	}
	

}
这里实现使用了FutureTask,那么FutureTask是做什么的呢?简单说就是FutureTask中持有Callable的实例且FutureTask间接实现了Future和Runnable接口( 准确来说FutureTask实现的是RunnableFuture接口,而RunnableFuture实现的是Future和Runnable接口 ),所以就实现了再Runnable的run方法中调用Callable的call方法。那么Future作用是什么呢?我们首先先看一下Future的JDK源码:

public interface Future {
	boolean cancel(boolean mayInterruptIfRunning);

	boolean isCancelled();

	boolean isDone();

	V get() throws InterruptedException, ExecutionException;

	V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

Future 其实就是用来表示异步计算的结果。只不过抽象成接口了。


所以证明了当初的结论:Callable其实类似于Runnable接口一样,其实例可以被线程执行。


2:接下来步骤其实就是步骤1中提及到过的FutureTask,创建FutureTask实例,将Callable引用传到其构造器中。由于FutureTask实现了Runnable接口,所以可以被线程执行,进而在线程的run方法中调用call方法完成任务的计算。

如下是JDK的FutureTask的run方法源码:

 public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable c = callable;//获取FutureTask持有的Callable的引用
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();//正式调用Callable的call方法
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }




3:最后通过FutureTask实例获取计算结果。

Future的get方法在FutureTask中的实现:(get是一个阻塞方法)

    /**
     * @throws CancellationException {@inheritDoc}
     */
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING) //COMPLETING是FutureTask任务执行的状态
            s = awaitDone(false, 0L);//没有计算完,进行等待,具体实现此处不展开
        return report(s);//如果计算完毕返回
    }

到此为止就完成了使用JDK的Callable实现线程!



接下来我们也模仿JDK的Callable简单实现一下自己的Callable、Future、FutureTask:



/**
 * 对应的是JDK中是Callable接口
 * 
 * @author Daxin
 *
 * @param 
 */
public interface Comand {

	T call() throws Exception;
	
}


/**
 * 抽象接口,Future表示异步计算的结果。
 * @author Daxin
 *
 * @param 
 */
public interface Future {
	
	public T getResult();

	public boolean isDone();
}



public class FutureTask implements Runnable, Future {

	/**
	 * 执行的任务
	 */
	Comand comandImpl = null;

	/**
	 * 计算结果
	 */
	T result = null;

	// 用于获取是否计算完成
	private volatile boolean isDone = false;// volatile关键字是为了实时更新isDone数值

	public FutureTask(Comand comandImpl) {
		super();
		this.comandImpl = comandImpl;
	}

	// 是一个阻塞方法
	@Override
	public T getResult() {
		if (isDone) {
			return result;
		}

		//简单模拟,每每0.2轮询是否计算完毕
		while (!isDone) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return result;

	}

	@Override
	public boolean isDone() {
		return isDone;

	}

	@Override
	public void run() {

		try {
			result = comandImpl.call();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		isDone = true;

	}

}





/**
 * 自己实现类
 *
 */
public class ComandImpl implements Comand {

	@Override
	public Integer call() throws Exception {

		int sum = 0;
		for (int i = 0; i <= 99; i++) {
			Thread.sleep(100);
			sum += i;

		}
		return sum;
	}

}


public class Main {
	public static void main(String[] args) {

		Comand cmd = new ComandImpl();
		FutureTask t = new FutureTask(cmd);

		new Thread(t).start();
		// System.out.println(t.isDone());
		// System.out.println(t.getResult());
		// System.out.println(t.isDone());
		
		
	for(;;){
		if(t.isDone()){
			System.out.println(t.getResult());
			break;
		}
	}
		

	}
}


拓展:

到此如果问Runnable和Callable有什么呢?

1:其中声明的方法不同,一个是run没有返回值,另一个是call有返回值。

2:run方法声明不允许向外抛异常 ,而call方法允许向外抛异常

3:在使用Callable实现线程时候,call方法是在run方法中被调用的


你可能感兴趣的:(Java)