在Java中,实现多线程的方式有3种,分别是继承Thread类、实现Runnable接口、实现Callable接口。关于它们是如何实现,有何区别,且听我慢慢道来。
方式一:继承Thread类
一个普通类只要继承了传说中的Thread类,那么,这个普通类就具备了多线程操作能力。
But,这个普通类必须重写Thread类中的run()
方法,因为我们所编写的代码要放在这个方法中,而且所编写的代码是一个线程体。
通过继承Thread类实现多线程实例:
MyThread类(普通类)
package cn.tkr.thread;
public class MyThread extends Thread {
//重写Thread类中的run()方法
@Override
public void run() {
//编写线程体(线程要执行的代码)
System.out.println("大家好,我是MyThread类中的run()方法中的代码");
}
}
TestMyThread类(测试类)
package cn.tkr.thread;
public class TestMyThread {
public static void main(String[] args) {
MyThread m = new MyThread(); //创建线程类的对象
m.start(); //启动线程
System.out.println("我是main方法中的代码");
}
}
运行结果:
大家好,我是MyThread类中的run()方法中的代码
我是main方法中的代码
实例分析:
在运行这个程序时,完美的启动了Java虚拟机,负责执行主线程(主方法)中的代码,在执行MyThread m = new MyThread()
之前有一个线程,线程名称为main,是主线程,我们使用m.start()
启动线程,这个时候就有两个线程了,一个是主线程,一个是m线程。
要注意一点,由于哪个线程先“抢占”到CPU资源不确定,所以这两个线程的哪个先执行也是不确定的。
方式二:实现Runnable接口
一个普通类实现了Runnable接口,那么,这个普通类也就具备了多线程操作能力。
But,Runnable接口中没有定义start()
方法,所以想要启动线程,我们还得拜托一下Thread类,毕竟人家有start()
方法。
通过实现Runnable接口实现多线程实例:
MyRunnable类(普通类)
package cn.tkr.thread;
public class MyRunnable implements Runnable {
//重写Runnable接口中的run()方法
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("我是MyRunnable类中的run方法中的代码" + i);
}
}
}
TestMyRunnable类(测试类)
package cn.tkr.thread;
public class TestMyRunnable {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); //创建线程类的对象
Thread m = new Thread(mr);
m.start();
for (int i = 0; i < 3; i++) {
System.out.println("我是main方法中的代码" + i);
}
}
}
运行结果:
我是main方法中的代码0
我是MyRunnable类中的run方法中的代码0
我是MyRunnable类中的run方法中的代码1
我是MyRunnable类中的run方法中的代码2
我是main方法中的代码1
我是main方法中的代码2
实例分析:
在Thread类中有个构造方法叫Thread(Runable target )
,专门用来接收线程类的对象(实现Runnable接口的线程类对象),把mr线程传递给Thread类之后,我们就可以通过对象m来完美地启动线程。
方式三:实现Callable接口
JDK1.5之后,冒出了一个叫Callable的接口,它的到来,让Runnable抑郁了,因为Callable太强大了。
与Runnable实现多线程相比,Callable支持泛型,call()
方法可以有返回值,而且还支持泛型的返回值,比run()
方法更强大的一点是,居然还能抛出异常。
But,Callable接口中的Call()
方法需要借助FutureTask
类获取结果。
FutureTask
类是RunnableFuture
接口的实现类,而RunnableFuture
接口又继承了Future
接口和Runnable
接口,所以FutureTask
类也是Runnable
接口的实现类。
注:FutureTask
类是一个任务管理器类
切,绕了这么一大圈,到头来,不还是要靠我们家Runnable
接口嘛。
总的来说,这种方式是通过创建FutureTask
类的对象将Callable
接口的实现类传入,从而实现多线程。
通过实现Callable接口实现多线程实例:
RandomCallable类(普通类)
package cn.tkr.thread;
import java.util.concurrent.Callable;
public class RandomCallable implements Callable<String> {
@Override
public String call() throws Exception {
String[] str = {"A","B","C","D","E"}; //创建一个数组,长度为5
int random = (int)(Math.random()*4)+1; //产生一个1~4之间的随机数
return str[random]; //根据产生的随机数返回数组中对应位置的字符串
}
}
TestRandomCallable类(测试类)
package cn.tkr.thread;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestRandomCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
RandomCallable rc = new RandomCallable(); //创建一个任务
FutureTask<String> ft = new FutureTask<>(rc); //创建一个任务管理器
Thread t = new Thread(ft);
System.out.println("任务是否已完成:" + ft.isDone());
t.start();
System.out.println(ft.get());
System.out.println("任务是否已完成:" + ft.isDone());
}
}
运行结果:
任务是否已完成:false
B
任务是否已完成:true
实例分析:
FutureTask类是Runnable接口的实现类,所以要启动线程我们还是要请Thread类来帮忙。
在使用start()
方法之前,任务是没有完成的,所以isDone()
的结果为false
,当我们开启线程后,用get()
方法获取到了结果,说明任务已完成,因为只有结果出来任务才能结束,否则无论get()
方法后面有多少句代码都不会执行的。
继承Thread类与实现Runnable接口的区别
大家都知道,Java类具有单继承的特点,如果一个类选择继承Thread类来实现多线程,那么这个类就不能再继承其他类了。
但是如果选择实现Runnable接口,不仅避免了单继承带来的局限性,还能方便共享资源,同一个资源可以有多个代理访问。
实例(三个窗口卖票问题):
在火车站的售票厅里,有三个窗口在卖5张票,每个窗口有30人在排队,那到底要使用哪种方式实现多线程呢?
第一种,使用继承Thread类的方式来实现
实例:
TicketThread类
package cn.tkr.thread;
public class TicketThread extends Thread {
private int tickets = 5;
public TicketThread (String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
if (tickets > 0){
System.out.println(super.getName() + "卖第" + (tickets--) + "张票");
}
}
}
}
TestTicketThread类(测试类)
package cn.tkr.thread;
public class TestTicketThread {
public static void main(String[] args) {
//创建三个线程类的对象
TicketThread t1 = new TicketThread("窗口一");
TicketThread t2 = new TicketThread("窗口二");
TicketThread t3 = new TicketThread("窗口三");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
窗口二卖第5张票
窗口三卖第5张票
窗口一卖第5张票
窗口三卖第4张票
窗口二卖第4张票
窗口三卖第3张票
窗口一卖第4张票
窗口三卖第2张票
窗口二卖第3张票
窗口三卖第1张票
窗口一卖第3张票
窗口一卖第2张票
窗口二卖第2张票
窗口一卖第1张票
窗口二卖第1张票
实例分析:
三个窗口同时卖票,我们就要创建三个线程。从运行结果看,本来只卖5张票,现在变成了15张了,很显然,private int tickets = 5
这个资源并没有共享。
这三个不同的线程类的对象,所用到的资源都是相互独立的,也就是说线程类对象t1、t2、t3用到的都是private int tickets = 5
这个资源,所以就会出现15张票。
再看另一种,使用实现Runnable接口的方式来实现
实例:
TicketRunnable类
package cn.tkr.thread;
public class TicketRunnable implements Runnable {
private int tickets = 5;
@Override
public void run() {
for (int i = 0; i < 30; i++) {
if (tickets > 0){
System.out.println(Thread.currentThread().getName() + "卖第" + (tickets--) + "张票");
}
}
}
}
TestTicketRunnable类(测试类)
package cn.tkr.thread;
public class TestTicketRunnable {
public static void main(String[] args) {
//创建线程类的对象
TicketRunnable tr = new TicketRunnable();
Thread t1 = new Thread(tr,"窗口一");
Thread t2 = new Thread(tr,"窗口二");
Thread t3 = new Thread(tr,"窗口三");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
窗口一卖第5张票
窗口二卖第4张票
窗口一卖第3张票
窗口二卖第2张票
窗口一卖第1张票
实例分析:
通过实现Runnable接口的方式实现多线程,达到了多个线程共享同一个资源的目的,完美地解决了使用Thread类来实现多线程出现的问题。
总结
以上是我分享给大家的关于多线程实现方式的一些总结。如果觉得还不错的话,就送我一个赞吧!如果本文对你有用的话,也欢迎收藏哦!