环境: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()方法!
以上内容整理自《阿里云课程》