Thread类、Runnable接口、Callable接口实现多线程的区别

环境:JDK14

一、继承Thread类实现多线程

Java中有一个java.lang.Thread的类,只要继承了此类就表示:这个类为线程的主体类,但还需要覆写Thread类中提供的一个run()方法,而这个方法就属于线程的主方法(主方法是不能有返回值),多线程要执行的功能都应该在run()方法中定义。

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread threadA = new MyThread();
        MyThread threadB = new MyThread();
        threadA.start();
        threadB.start();
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("这是run方法执行");
    }
}

注意:run() 方法是不能被直接调用的,涉及到操作系统的资源调度问题,所以要想启动多线程,必须使用Thread类中提供的start( )方法完成。
为什么多线程的启动不直接使用run( )方法而必须使用Thread类中的start( )方法?
直接查看start()方法的源码:

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

可以看到,这里主要的功能就是调用了start0()方法;(每个线程类的对象只允许启动一次,如果重复启动则抛出“IllegalThreadStateException”异常)

   private native void start0();

而且在源码中对于start0()方法是只定义了方法名,但没有实现,因为: Java中 执行过程中考虑到不同层次的开发者需求,所以其支持本地的操作系统函数调用,该技术被称为JNI(Java native interface)技术,但是Java开发中并不推荐这样使用,可使用一些操作系统提供的底层函数进行一些特殊处理,而在Thread类中提供的start0()就表示此方法依赖于不同操作系统实现,即JVM实现了start0()的方法
在这里插入图片描述
所以记住: 任何情况下,只要定义了多线程,多线程启动永远只有一种方案:Thread类中的start( )方法

二、基于Runnable接口实现多线程

虽然继承Thread类已经实现多线程的定义,但是始终存在单继承局限的,所以又提供了第二种多线程的主体定义结构形式:实现 java.lang.Runnable接口

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface {@code Runnable} is used
     * to create a thread, starting the thread causes the object's
     * {@code run} method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method {@code run} is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

该接口只有一个run()方法,属于函数式接口
那么问题来了:既然不再继承Thread父类了,此时在也就不再支持有start( )方法。但是不使用Thread.start()方法是无法进行多线程启动的,观察Thread类的构造方法——可接收Runable实例

  public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }

基于Runable接口实现多线程:

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread threadA = new MyThread();
        MyThread threadB = new MyThread();
        Thread tA = new Thread(threadA);
        Thread tB = new Thread(threadB);
        tA.start();
        tB.start();
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println("这是run方法执行");
    }
}

注意:一定要使用Thread类实例调用start()方法才能启动多线程
此时就没有单继承的局限性,所以开发中,优先考虑的就是Runnable接口的实现,并且最后都是通过Thread类启动多线程

三、Thread与Runnable的关系

public class Thread implements Runnable{  }

Thread类是Runnable接口的子类
所以“继承Thread类实现多线程”实际上覆写的还是Runnable中的run()方法,整体结构:
在这里插入图片描述
说明:

  • 多线程设计中,使用了代理设计模式的结构——用户自定义的线程主体只是负责项目核心功能实现,而其它辅助实现都交由Thread()类实现
  • 在Thread启动多线程的时候调用的是start()方法,而后找到的是run()方法——用户自定义覆写。
  • 当通过Thread类的构造方法传递了一个Runnable接口对象的时候,那么该Runnable接口对象将被Thread类中的target属性所保存,在start()方法执行的时候会调用Thread类的run()方法,而这个run()方法去调用Runnable接口子类被覆写过的run()方法
    整个过程:创建Runnable实例->传入Thread类中->调用Thread类中的start()方法启动线程;
    在这里插入图片描述

四、Callable实现多线程

实际开发中,进行多线程的实现肯定依靠的是 Runnable接口,但是Runnable接口有一个缺点就是:当线程执行完毕之后,无法获取一个返回值。 所以在 JDK1.5之后提供一个新的线程实现接口:java.util.concurrent.Callable接口
该接口源码:

@FunctionalInterface
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;
}

可发现Callable定义的时候可以设置一个泛型,此泛型的类型就是返回数据的类型,这样的好处是可避免向下转型的安全隐患;
要想启动多线程:唯一的办法就是调用 Thread类中的start()方法
问题:Callable是怎么实现多线程启动的呢?
1. 在 java.util.concurrent包中有一个 FutureTask <> 类

public class FutureTask<V> implements RunnableFuture<V> {

}

2.观察 RunnableFuture<>接口

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

可见FutureTask<>类是Runnable的子类,实现FutureTask接口肯定实现了Runnable接口,这样就可调用Thread类中的start()方法启动线程;而在Future<>接口中有get()方法可获取线程返回值

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

所以整个关系图就是:
在这里插入图片描述
面试题:请解释Runnable与Callable的区别?

  • Runnable是在 JDK1.0时候提出的多线程的实现接口,而Callable是在JDK1.5后提出的
  • java.lang.Runnable接口中只提供了一个run()方法,并且没有返回值
  • java.util.concurrent.Callable接口提供有call()方法,可以有返回值

范例:实现一个竞拍抢答程序,要求设置三个抢答者(三个线程),而后同时发出抢答指令,抢答成功返回true,否则给出失败提示。
对于一个多线程的操作由于需要设计到数据的返回问题,那么最好使用的是Callable实现多线程。

		package link;
   	
   	import javax.print.DocFlavor;
   	import java.util.concurrent.Callable;
   	import java.util.concurrent.ExecutionException;
   	import java.util.concurrent.FutureTask;
   	
   	/**
   	 * @author lkh
   	 * @date 2020/10/23 11:33
   	 * @description 多线程操作
   	 */
   	
   	public class ThreadDemo {
   	    public static void main(String[] args) throws ExecutionException, InterruptedException {
   	        MyThread mt = new MyThread();
   	        FutureTask<String> taskA = new FutureTask<String> (mt);
   	        FutureTask<String> taskB= new FutureTask<String> (mt);
   	        FutureTask<String> taskC = new FutureTask<String> (mt);
   	        new Thread(taskA,"A选手").start();
   	        new Thread(taskB,"B选手").start();
   	        new Thread(taskC,"C选手").start();
   	        System.out.println(taskA.get());
   	        System.out.println(taskB.get());
   	        System.out.println(taskC.get());
   	    }
   	}
   	class MyThread implements Callable<String>{
   	    private boolean flag = false;
   	    @Override
   	    public String call() throws Exception {
   	        synchronized (this){
   	            if (!this.flag) { // 抢答成功
   	                this.flag = true;
   	                return Thread.currentThread().getName()+"抢到了!!";
   	            }else{
   	                return Thread.currentThread().getName()+"未抢到!!";
   	            }
   	        }
   	    }
   	}

重点:实现多线程的启动一定是调用Thread类中start()方法!

以上内容整理自《阿里云课程》

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值