Java实现多线程的两种途径
- 继承Thread类
- 实现Runnable接口(Callable)接口
一、继承Thread类
Thread类是一个支持多线程的功能类,只要有一个子类它就可以实现多线程的操作
class MyThread extends Thread{ //这就是一个多现成的操作类
}
所有程序的起点是main()方法,但是所有的线程一定要有自己的起点,那就是run()方法,也就是说在多线程主体类,每个主题类之中必须覆写Thread类中提供的run()方法。
public void run(){}
这个方法上没有返回值,表示线程一旦开始就要一直执行,不能够返回内容
class MyThread extends Thread{ //这就是一个多线程的操作类
private String name; //定义类中的属性
public MyThread(String name) { //定义构造方法
this.name = name;
}
@Override
public void run() { //覆写run()方法作为线程的主体操作方法
for(int x = 0; x < 200 ; x++) {
System.out.println(this.name + "-->" + x);
}
}
}
本现场功能是进行循环的输出操作,所有的线程与进程是一样的,都必须轮流去抢占资源,多线程的执行应该是多个线程彼此交替执行,也就是说如果直接调用了run()方法,并不能直接启用线程,启用线程是Thread类中的start()方法
public void start() // 调用此方法执行的方法体是run()方法
执行代码如下:
public class TestDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
MyThread mt3 = new MyThread("线程C");
mt1.start();
mt2.start();
mt3.start();
}
}
此时每一个线程对象交替执行
此样例的全部代码如下:
class MyThread extends Thread{//这就是一个多线程的操作类
private String name;//定义类中的属性
public MyThread(String name) {//定义构造方法
this.name = name;
}
@Override
public void run() {//覆写run()方法作为线程的主体操作方法
for(int x = 0; x < 200 ; x++) {
System.out.println(this.name + "-->" + x);
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
MyThread mt3 = new MyThread("线程C");
mt1.start();
mt2.start();
mt3.start();
}
}
疑问?为什么多线程的启动不是调用run()而是调用start()?
使用Thread类的start()方法不仅仅要启动多线程的执行代码,还要根据不同的操作系统进行资源的分配
二、实现Runnable接口
虽然Thread类可以实现线程的主体类定义,但是它还有一个问题,Java具有单继承局限,正因为如此在任何情况下针对于类的继承都应该回避的问题,那么多线程也一样,为了解决单继承的限制,在Java里边专门提供了Runnable接口,此接口定义如下
@FunctionalInterface
public interface Runnable{
public void run();
}
在接口里面任何的方法都是public定义的权限,不存在默认的权限
只需要让一个类实现Runnable接口即可,并且也需要覆写run()方法。
class MyThread implements Runnable{//这就是一个多线程的操作类
private String name;//定义类中的属性
public MyThread(String name) {//定义构造方法
this.name = name;
}
@Override
public void run() {//覆写run()方法作为线程的主体操作方法
for(int x = 0; x < 200 ; x++) {
System.out.println(this.name + "-->" + x);
}
}
}
与继承Thread类相比,此时的MyThread类在结构上是没有区别的,但是有一点是有严重区别的,继承了Thread类,那么可以直接继承run()方法,但是如果实现Runnable接口,并没有start()方法可以被继承
不管何种情况下,如果要启动多线程一定要依靠Thread类完成,在Thread类里面定义有以下的构造方法
- 构造方法:public Thread(Runnable taget),接收Runnable接口对象;
范例:启动多线程
public class TestDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
MyThread mt3 = new MyThread("线程C");
new Thread(mt1).start();
new Thread(mt2).start();
new Thread(mt3).start();
}
}
此时就避免了单继承局限,在实际工作中使用Runnable接口是最实用的
三、多线程两种实现方式区别?(面试题)
使用Runnable接口与Thread类相比,解决了单继承的定义局限,所以不管后面区别是什么,这里下了死定义——如果要使用一定使用Runnable接口。
使用Runnable接口可以比Thread类能够更好的描述出数据共享这一概念。此时的数据共享是多个线程访问统一资源的概念 。
观察:
class MyThread extends Thread{//这就是一个多线程的操作类
private int ticket = 10;
@Override
public void run() {//覆写run()方法作为线程的主体操作方法
for(int x = 0; x < 100 ; x++) {
if(this.ticket > 0) {
System.out.println("卖票,ticket = " + this.ticket --);
}
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
mt1.start();
mt2.start();
mt3.start();
}
}
本程序声明了三个MyThread类的对象,并且分别调用了三次start()方法,启动线程对象,发现每个线程对象都在卖各自的10张票,此时并不存在数据共享这一概念
范例:利用Runable来实现
class MyThread implements Runnable{//这就是一个多线程的操作类
private int ticket = 10;
@Override
public void run() {//覆写run()方法作为线程的主体操作方法
for(int x = 0; x < 100 ; x++) {
if(this.ticket > 0) {
System.out.println("卖票,ticket = " + this.ticket --);
}
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
虽然也是三个线程对象,可以唯一的区别是这三个线程对象都直接占用了同一个MyThread类的对象引用,也就是说这三个线程对象访问的是一个数据资源。
面试题:请解释Thread类与Runnable接口实现多线程的区别?(面试题:请解释多线程两种实现的区别?)
- Thread类是Runnable接口的子类,使用Runnable接口实现多线程可以避免单继承局限;
- Runnable使用的多线程可以比Thread类实现的多线程更加清楚的描述数据共享的概念:
面试题:请写出多线程两种实现操作。
把Thread类继承的方式和Runnable接口实现的方式都写出来。
四、第三种实现方式(理解)
使用Runnable接口实现的多线程可以避免单继承局限,的确很好,但是有一个问题,Runnable接口里边的run()方法不能返回操作结果。为了解决这样的矛盾,提供了新的接口Callable接口。
@FunctionalInterface
public interface Callable<V>{
public V call() throws Exception;
}
call()方法执行完线程的主题功能之后可以返回一个结果,而返回结果的类型由Callable接口上的泛型决定。
范例:定义一个线程主体类
class MyThread implements Callable<String>{//这就是一个多线程的操作类
private int ticket = 10;
@Override
public String call() throws Exception {
for(int x = 0 ; x <100 ; x++) {
if(this.ticket > 0) {
System.out.println("卖票,ticket = " + this.ticket --);
}
}
return "票已卖光!";
}
}
此时观察Thread类里边并没有直接支持Callable接口的多线程应用
在FutureTask类里边定义有如下构造方法:public FutureTask(Callable<V> callable)
接受的目的只有一个,那么就是取得call()方法的返回结果。
public class TestDemo {
public static void main(String[] args) throws Exception {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
FutureTask<String> task1 = new FutureTask<>(mt1);//目的是为了取得call的返回结果
FutureTask<String> task2 = new FutureTask<>(mt2);//目的是为了取得call的返回结果
//FutureTask是Runnable接口子类,所以可以使用Thread类的构造来接收task对象
new Thread(task1).start();//启动多线程
new Thread(task2).start();//启动多线程
//多线程执行完毕后可以取得内容,依靠FutureTask的父类接口Future中的get()方法完成
System.out.println("A线程返回的结果:" + task1.get());
System.out.println("B线程返回的结果:" + task2.get());
}
}
最麻烦的问题在于需要接收返回值信息,并且又要与原始的多线程实现靠拢