Java并发编程笔记-自定义Runnable

Runnable

Runnable 是一个接口,是线程的执行体。源码非常的简单。

public interface Runnable{
	public abstract void run();
}

为了应对复杂的场景,需要对Runnable进行简单的自定义,下面就描述两种场景,已经我对这两种场景的思考。
场景1:获取线程(执行体)的执行结果。
场景2:优雅的停止线程。

获取线程(执行体)的执行结果

注意
本文中的执行体可能是Runnable 中的run方法或者Callable中的Call方法。线程的执行结果等价与执行体的执行结果。

Callable,Future

需要获取执行体run的执行结果,可以使用java提供的Callable,Future相关接口。实现例子如下

    public class MyCallable implements Callable<String>{
        @Override
        public String call() throws Exception {
            doSomething();
            return "执行完成";
        }
        private String doSomething(){
            return null;
        }
    }
    public void test1(){
        FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
        new Thread(futureTask).start();
        doOtherthing();
        // 获取结果
        try {
            String  ret = futureTask.get();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

通过Callable,Future相关类获取执行结果的好处有:

  1. 编写简单,使用java提供的原生接口。
  2. 如果执行体(Callable中的call 方法)有异常,可以在调用线程获取到异常。
  3. FutureTask 的 get 方法是阻塞方法,保证执行体一定能够执行完,并且获取到结果。
  4. 开启线程之后,可以执行其他任务,在需要线程结果的时候,调用FutureTask的get方法即可。例如例子中doOtherthing(),就是在开启线程后并行的任务。

缺点:
由于FutureTask 的 get 方法是阻塞的,因此在一些禁止执行耗时的线程中,调用这个方法是不明智的。例如在Android 系统中 UI 主线程中需要开启一个线程执行耗时任务,同时又想获取线程的执行结果,这时候调用FutureTask 的 get会造成UI 主线程的阻塞,容易造成ANR异常。

使用监听器回调,获取线程执行结果

为了能够在禁止执行耗时操作的线程中(比如说Android 中的UI 主线程)获取被调用线程的执行结果,可以自定义Runnable 或者 Callable来满足这个条件。示例代码如下

    public class MyRunnable<T> implements Runnable{
        final RunnableListener<T> listener;

        public MyRunnable(RunnableListener<T> listener) {
            this.listener = listener;
        }
        @Override
        public void run() {
            T result = null;
			result = doSomething();
            listener.onResult(result);
        }
    }
    interface RunnableListener<T>{
        void onResult(T result);
    }
    public void test2(){
        new Thread(new MyRunnable<String>(new RunnableListener<String>() {
            @Override
            public void onResult(String result) {
                System.out.println("获取到结果: "+result);
            }
        })).start();
        // 调用者和开启线程是并发的,但是执行doOtherthing及其后续操作的时候,不能保证开启的线程已执行回调函数
       doOtherthing();
    }

使用监听器回调,获取线程执行结果的好处是:

  1. 避免了调用线程的阻塞。
  2. 在步骤式的场景中非常使用,保证了按步执行。

使用监听器回调,获取线程执行结果的好处是缺点是:

  1. 由于在监听中才能获取到结果,因此被调用线程无法确切知道线程执行结果的时间。
  2. 由于监听器是在线程中调用,因此监听器和被调用线程处于同一线程,如果监听结果需要在调用线程中执行,需要进行线程切换
  3. 此方法在步骤式场景中有较好的效果,但是不能适应复杂的场景,比如说获取异常,监听状态,虽然通过复杂的自定义Runnable可以实现,但是这个跟Callable的差不多,因此采用Callable更为合理。

在复杂场景中可以将两者结合使用,当然我们可以定义Runnable或者Callable,具体根据需求而定。自定义Runnable伪代码如下:

public class MyRunnable<T> implements Runnable{
        final RunnableListener<T> listener;

        public MyRunnable(RunnableListener<T> listener) {
            this.listener = listener;
        }
        @Override
        public void run() {
            T result = null;
            // 创建其他线程,执行耗时操作
			FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
			// 线程中阻塞等待结构
        	new Thread(futureTask).start();
        	// 将结果通过监听回调返回到调用线程中。
            listener.onResult(result);
        }
    }

其他方法获取线程执行结果

当然还有其他方式获取线程的执行结果,比如说采用线程通信方式,信号量,消息队列等等。

优雅的停止线程

停止线程的方式有三种:

  1. 调用Thread中的stop方法。这种方法不推荐,而且在Android 系统中直接抛出异常 UnsupportedOperationException
  2. 定义通过 标志位 来终止线程
  3. 通过 Thread.currentThread().isInterrupted() 来判断

方法2和方法3本质都是相同的,都是通过标志位来判断执行体是否需要继续执行。通过标志为来判断,简要代码示例如下

private static class Runner implements Runnable{
		private long i;
		private (volatile) boolean running =true;
		@Override
		public void run() {
			System.out.println("current Thread Name:"+Thread.currentThread().getName());
			while (running ){
				i++;
                try {
                	if(aa){
                		break;
                	}
					TimeUnit.SECONDS.sleep(10);
				} catch (InterruptedException e) {
				}
			}
			System.out.println("Count i= "+i);
			System.out.println("current Thread Name:"+Thread.currentThread().getName());
			
		}
		public void cancel(){
			running =false;
			System.out.println("running=false");
		}
	}

缺点:

  • 问题就是一旦线程运行过了running ,那么需要再次运行到判断running的位置才能判断执行体是否需要执行。
  • cancel 接口太独特,如果无法获取到Runner 的实例,则无法调用响应的接口。
    通过 Thread.currentThread().isInterrupted() 来判断
     class TestRunnable1 implements Runnable{
         @Override
         public void run() {
             try{
                 while (!isInterrupted()){
                     Log.d(TAG,"time = "+ DateUtil.getCurrentTime("yyyy-MM-dd HH:mm:ss"));
                     Thread.sleep(10);
                 }
             }catch (InterruptedException e){
                 System.out.println("线程中断,退出执行");
             }
         }

         /**
          * 在使用非当前线程资源的时候调用一下,检查当前线程的状态
          * @return
          * @throws InterruptedException
          */
         private boolean isInterrupted() throws InterruptedException{
             if (Thread.currentThread().isInterrupted()){
                 throw new InterruptedException();
             }
             return false;
         }
     }
     // 调用
     Thread thread = new Thread(new TestRunnable1 ());
     thread.interrupt();

通过 Thread.currentThread().isInterrupted() 来判断,和 标志位判断差不多,但是这样做的好处有;

  1. Thread.currentThread().isInterrupted(),是java提供的方法。在Future 的 cancel ,线程组 ThreadGroup.interrupt() 都是调用这个方法,保证了统一性。
  2. InterruptedException 异常在线程的阻塞状态,等等状态都有抛出,这样可以进行一个统一处理。
  3. 总之使用Thread.currentThread().isInterrupted的好处就是保证了统一性。

注意

  1. 通过 Thread.currentThread().isInterrupted() 来停止线程,显然要比标示位优雅一些,但要防止条件判断永远无法生效。因此我个人觉得在调用非本线程资源是可以调用一下,因此,我封装了一个简单的接口isInterrupted()。提高了一点调用的方便性。
  2. 注意 interrupt,interrupted,isInterrupted 区别
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页