Java多线程——多线程的四种实现方式
前言:
本篇文章有几个必要重要的知识点
1、启动线程的注意事项
2、继承Thread类与实现Runnable接口的关系
3、Callable与Thread、Runnable的关系
一、直接继承Thread,覆写run()方法
class MyThread1 extends Thread{
private String title;
public MyThread1(String title){
this.title = title;
}
@Override
public void run() {
for (int i = 0;i<10;i++) {
System.out.println(title+":"+i);
}
}
}
这里覆写的run()方法相当于主线程的main()方法,是一个线程的入口方法。
启动线程的注意事项
(具体流程请参考:线程启动函数start()源码解析)
1、无论是哪种方式实现多线程,线程启动一定调用Thread类提供的start()方法
<而不是run()方法>
2、线程start()方法只能调用一次
,再次调用就会抛出线程非法状态(启动)异常(java.lang.IllegalThreadStateException)
3、start()方法的具体流程:start(判断当前线程是不是首次创建,Java方法)->调用start0()方法(JVM)->通过JVM进行资源调度,系统分配->回调run()方法(Java方法)执行线程的具体操作任务。
4、run()方法与start()的区别
:如果线程直接调用run()方法,当且仅当run()只是一个普通方法
;如果线程是通过start()方法(JVM回调)调用的run()方法,则可以实现线程的开启。
二、实现Runnable接口,覆写run()方法
由于在Java语言中,类具有单继承局限,因此实现Runnable接口可以使得:多个线程共享一个Runnable对象,通过实现Runnable接口的方式实现多线程更能体现出“共享”的概念(创建一个Runnable接口对象,并传入到多个new Thread(Runnable)中)
class MyRunnable implements Runnable{
private String title;
private Integer ticket = 10;
public MyRunnable(String title){
this.title = title;
}
@Override
public void run() {
for (int i=0;i<10;i++) {
System.out.println(this.title+"卖了一张票,还剩"+(ticket--)+"张票");
}
}
}
最终一定要使用start()来开启线程——通过Thread(Runnable),此时的Runnable可以使用匿名内部类/lambda表达式(只有一个run()方法)
// 调用已存在的Runnable对象启动一个线程
new Thread(runnable).start();
// 使用匿名内部类启动一个线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
// 使用lambda表达式启动一个线程(非常简便)
new Thread(()-> System.out.println(Thread.currentThread().getName())).start();
继承Thread类与实现Runnable接口的关系
1、继承具有单继承局限,而实现接口不限个数。
2、Thread类与自定义线程类(实现了Runnable接口),是一个典型的代理设计模式,Thread类负责辅助真实业务操作(资源调度,创建线程并启动),自定义线程类负责真实业务的实现(run方法具体要做啥事)
3、使用Runnable接口实现的多线程程序类可以更好的描述共享的概念。多个线程可以共同享用一个Runnable对象。
三、实现Callable接口,覆写call()方法
当线程有返回值时,只能通过实现Callable实现多线程 ,Callable接口在juc(java.util.concurrent
)包下。
// 覆写call()方法,而不是run()方法可
V call() throws Exception;
举个栗子
class MyCallable implements Callable<String>{
private int ticket = 10;
@Override
public String call() throws Exception {
while(this.ticket>0){
System.out.println(Thread.currentThread().getName()+",剩余票数:"+this.ticket--);
}
return "票卖完了";
}
}
Callable接口与Thread、Runnable的关系
从图中可以看到
① Future接口中get()方法的作用是: 取得Callable接口的返回值。
②RunnableFuture接口即继承了Runnable接口又继承了Future接口(接口的多继承),因此RunnableFuture接口拥有所继承的所有方法。
③ FutureTask(Callable实现多线程的核心类)
即是Runnable的子类,又是RunnableFuture的子类,又可以传入Callable(FutureTask的构造方法)——连通三者,因此FutureTask的作用为:
a)将Callable转为FutureTask
b)将FutureTask转为Thread(因为FutureTask间接实现了Runnable接口,相当于是Runnable)
c)使用Thread的start()方法启动线程——兜兜转转启动线程还是只能使用Thread的start()方法
因此,使用Callable接口的方式启动线程变为了:
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 先取得Callable对象
MyCallable myCallable = new MyCallable();
// 将Callable对象转换为FutureTask对象
FutureTask<String> futureTask = new FutureTask<>(myCallable);
// 由于FutureTask对象实现了RunnableFuture接口,
// 而RunnableFuture接口又是Runnable接口的子接口,
// 因此可以将FutureTask对象当做Runnable接口的实现类传给Thread
Thread thread1 = new Thread(futureTask);
Thread thread2 = new Thread(futureTask);
// 一切的一切,最终的最终,开启线程还得是Thread的start方法
thread1.start();
thread2.start();
// 打印线程的返回值
// 由于FutureTask间接实现了Future接口
// 因此可以使用FutureTask的get()方法获取线程的返回值
System.out.println(futureTask.get());
}
// 打印结果如下:
Thread-0,剩余票数:10
Thread-0,剩余票数:9
Thread-0,剩余票数:8
Thread-0,剩余票数:7
Thread-0,剩余票数:6
Thread-0,剩余票数:5
Thread-0,剩余票数:4
Thread-0,剩余票数:3
Thread-0,剩余票数:2
Thread-0,剩余票数:1
票卖完了 // 线程的返回值