深入理解Java线程的创建

最近再次阅读了java并发编程的相关书籍和博客,以此为机会对自己学习到的知识做一个总结。

创建线程

Runnable 和 Thread

很多博客说创建线程有两种方式一种是通过java.lang.Thread来实现,一种是实现接口java.lang.Runnable。对此有我不同的看法。我认为创建线程只有一种方式那就是通过java.lang.Thread来实现。接口java.lang.Thread只是编写了线程的执行体。最终创建线程还是需要通过Thread来实现。
使用Runnable的例子

  1. 实现Runnable接口
    class MyRunnable implements  Runnable{
        @Override
        public void run() {
            System.out.println("MyRunnable 线程ID="+Thread.currentThread().getId());
        }
    }
  1. 调用Runnable
System.out.println("调用者 线程ID="+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
  1. 通过Thread来调用Runnable
System.out.println("调用者 线程ID="+Thread.currentThread().getId());
new Thread(runnable).start();

通过打印信息可以知道直接调用Runnable的方法,调用Runnable和Runnable的run方法在同一个线程,线程ID相同。而通过Thread来调用Runnable,调用者和Runnable的run方法在不同的线程,线程ID不相同。因此可知,Runnable接口只是线程的执行体,创建线程只有一个方式就是通过Thread的方式。

查看Thread的源码

public class Thread implements Runnable{
	@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

可以这么理解,Runnable的run方法是执行主体,Thread的功能是创建一个线程,在线程中调用run方法。即在线程中执行函数。

Thread 中start和Run的区别

在很多面试或者笔试中,会问Thread中start和Run的区别。首先run的本质就是执行Runnable(target是Runnable的实例)的run方法,就是普通的调用函数,因此就能够理解run方法和调用者是同一个线程,run方法可以重复多次调用,run方法会阻塞当前线程,只要把run当做普通的调用实例中的方法就可以很好的理解了。
那么start呢。查看start的源码

public synchronized void start() {
...
	group.add(this);
	boolean started = false;
        try {
            start0();
            started = true;
        }
 ...       
}
private native void start0();

从简要的代码可以确认,start会修改状态,核心的是调用native方法实现线程的调用。因此star()方法是用于创建线程,不能被多次调用的就好理解了。

Callable,Future 相关类

将Runnable理解为线程的执行体,那么Callable,Future相关的类就好理解了。
既然有了Runnable这个线程的执行体,可以在线程中调用相关方法了为什么要设计Callable,Future相关的类呢,这个要从Runnable存在的问题来理解。

  1. Runnable 中的run 方法无返回值,因此无法获知执行体的执行结果。
  2. Runnable 中的run 方法只能在run中处理异常,无法将异常抛给调用者。
  3. Runnable 中无相关接口,修改和观察线程执行状态。

注意
以下所说的执行体是Callable中的call方法。

根据Runnable 中存在的问题,就可以设计一个接口或者类来解决这些问题。因此可以把接口Future看做是Runnable的功能扩展。Future的源码如下:

public interface Future<V>{
	// 提供接口获取执行的执行结果 两个get的区别只是增加了超时机制
	// 通过在获取结果的过程中抛出异常
	 V get() throws InterruptedException, ExecutionException;
	 V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
        // 提供接口中断线程,即中断执行体的执行
        boolean cancel(boolean mayInterruptIfRunning);
        // 提供接口观察执行的执行状态
        boolean isCancelled();// 是否被取消
        boolean isDone(); // 是否执行完成    
}

因为Threa的执行体,一定是实现了Runnable接口的因此,需要将Runnable和Future进行一个结合,形成了RunnableFuture。RunnableFuture的源码如下

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

扩展功肯定需要一个类来实现,因此就有了FutureTask。

获取执行的执行结果

下面着重分析FutureTask,看源码是如何解决以上问题的。

  • 解决第一问题,获取执行体的执行结果。
    这时候要把线程的执行体放在一个有返回值,可以跑出异常的方法中,然后在run中调用这个方法,就能获取到执行体的执行结果。
    Callable的源码
public interface Callable<V> {
	V call() throws Exception;
}

线程执行的时候运行run方法。这时候会调用我们定义好的执行体Callable。

public class FutureTask<V> implements RunnableFuture<V>{
		// 线程运行的时候执行这个方法
	    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
        	// 出现callable
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                	// 获取callable
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    // 保存异常
                    setException(ex);
                }
                if (ran)
                    set(result);//保存结果
            }
        } finally {
            runner = null;
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
}

保存执行结果

	protected void set(V v) {
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = v;// 保存执行结果
            U.putOrderedInt(this, STATE, NORMAL); // final state
            finishCompletion();
        }
    }

小结
为了获取到执行体的执行结果,将执行体由原来的实现Runnable中的run方法,改为实现Callable中的Call方法。为了能够复用原来已经设计好的Thread,保证Thread线程执行方法时能够调用到Callable的call方法。因此在run中想一些办法执行到Call方法,因此有了FutureTask中的Run中调用Callable中的Call方法。

获取执行体抛出的异常

线程Thread的执行主体是run方法,run方法是无法抛出异常的,因此需要将异常进行保存。参见FutureTask的run方法,就是在抛出异常的时候,调用函数setException保存了执行体(此时为Callable中的call方法)抛出的异常,在调用者调用get的时候可以获取到异常。这样做的好处也是保证了异常可以在想要获取到的地方获取。源码如下

  • 保存异常
protected void setException(Throwable t) {
		// 比较状态
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
        	// 保存异常
            outcome = t;
            U.putOrderedInt(this, STATE, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }
  • 获取异常
	public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);// 阻塞等待执行体执行完成,内部有超时等待机制,中断异常,
        return report(s); // 返回结果或者抛出异常
    }
    // 抛出执行体中的异常,或者返回执行执行结果
    private V report(int s) throws ExecutionException {
   		 // outcome 如果执行体正常执行完成,是执行体的执行结果,如果执行体抛出异常,outcome保存的是异常
        Object x = outcome; 
        // 执行体正常执行完成,返回结果
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        // 执行体抛出异常,结束执行,抛出异常    
        throw new ExecutionException((Throwable)x);
    }
  • 将FutureTask 和 Thread 结合实现线程
class FirstCallable implements Callable<String>{

        @Override
        public String call() throws Exception {
            for (int i = 0; i< 3;i++){
            	if (i == 2) {
					throw new RuntimeException("抛出异常");
				}
                System.out.println("FirstCallable "+i);
                try {
                    Thread.sleep( 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return "success";
        }
    }
    private void doSomething() {
		
	}
	
	public void funcTest1() {
		FutureTask<String> futureTask = new FutureTask<>(new FirstCallable());
        new Thread(futureTask).start();
        // 假设函数执行耗时操作,在操作的过程中FutureTask 已抛出异常
        // 执行过程中不需关心 FutureTask 的执行情况
        doSomething();
        try {
        	// 在获取执行体执行结果的时候,如果FutureTask有异常,这时候可以捕获到
			String result = futureTask.get();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

获取执行体的执行状态

原Runnable接口,是无法通过其接口控制run方法的执行,比如说想停止执行体的执行等等,后面有一小结,专门将这个问题。Future 为了解决这个问题提供了专门的接口

public interface Future<V>{
	// 取消执行体的执行
	boolean cancel(boolean mayInterruptIfRunning);
	// 获取执行体是否取消
	boolean isCancelled();
	// 获取执行体是否执行完成(由于这个方法是非阻塞的,可以通过这个接口来遍历,如果执行体执行完成,再去获取结果)
	boolean isDone()
}

FutureTask 是如何实现Future的接口的

  • 停止执行体的执行
public class FutureTask<V> implements RunnableFuture<V> {
	public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&
              U.compareAndSwapInt(this, STATE, 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();// 本质还是通过interrupt来停止线程
                } finally { // final state
                    U.putOrderedInt(this, STATE, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }
}

通过源码可以看出,FutureTask 中断执行体的执行,是通过接口interrupt来设置线程的状态,并没有直接中断执行体的执行,因此在执行体中需要通过接口isInterrupted接口来判断当前现在的状态,以此来决定执行体是否继续执行。
示例如下

class FirstCallable implements Callable<String>{

        @Override
        public String call() throws Exception {
            for(int i = 0;i< 1000;i ++) {
            	if (!Thread.currentThread().isInterrupted()) {
            		// 当前的线程已经被外部停止,这时可以中断执行体的执行。
					break;
				}
            	System.out.println("number = "+i);
            }
            return "success";
        }
    }

获取执行体的执行状态
源码如下

public boolean isCancelled() {
        return state >= CANCELLED;
    }

    public boolean isDone() {
        return state != NEW;
    }

通过isCancelled和isDone的实现可以获知,这两个接口返回的是FutureTask 的状态,并不能真实的反应执行体的执行状态,因此执行体需要特别注意,监听FutureTask 的状态或者Thread(线程的状态),通过这些状态,执行体执行相应的操作,保证执行体和FutureTask 是同一状态。

小结

最后通过一个类图,总结一下关于线程创建的问题。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值