Java 中多线程的实现方式 ( JDK1.5之后增加了第三种 )
实现多线程的方式
- 继承 Thread 类
- 实现 Runnable 接口 ( Callable 接口 )
继承 Thread 类
// 线程操作主类
// 这就是一个多线程操作类
class MyThread extends Thread {
}
注意:在多线程的每个主体类中都必须覆写 Thread
类中提供的 run()
方法。同时因为 run()
方法没有返回值,所以代表线程一旦开启,就无法停止,需要一直执行下去。
@Override
public void run() {
super.run();
}
Demo
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(this.name + " --> " + i);
}
}
}
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.run();
mt2.run();
mt3.run();
}
}
由此,我们创建了一个主类以及一个线程的主类,线程的主类继承了Thread
,从而实现了线程的操作。
在run()
方法中我们实现了一个功能,循环200次输出一行文字。并且在主类中通过创建对象的方式,创建了三个对应的线程。
但是通过控制台的输出我们发现,效果并不是我们想要的。我们想要的是三个线程互相争夺资源从而输出,而实际上是三个线程依次输出。所以我们这样写的是有问题的。
实际上,之所以无法出现预计出现的效果,是因为我们不能用run()
方法开启线程,我们需要用start()
方法开启线程。而start()
方法执行的内容便是run()
方法中写的内容。
所以我们需要把main
方法中的语句修改成:
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();
}
由此,多线程的第一个例子就搞定了。不过由于现在的电脑的配置都比较不错,所以程序运行都是一瞬间的事情。有条件的可以找台比较老旧的电脑测试一下。但是在这个例子中也是可以看到一点端倪的。
当然,如果看过多线程的源码就会看到。如果一个线程重复启动的话,就会抛出一个异常IllegalThreadStateException
。
实现Runnable接口
上面聊了聊 Java 实现多线程的一种方式 - 继承Thread
类。但是这种方式是有比较大的问题的。为什么呢?因为在 Java 中,类是单继承的,不能继承多个类。所以这就导致了一定的局限性,那么不管是多线程也好,还是其他的点也好,都是需要规避的一个地方。而这部分要聊的是实现Runnable
接口。在 Java 中一个类不能继承多个类,但是可以实现多个接口,相对于继承Thread
,这种方式就没有那么大的局限性了。
那么首先看如何实现Runnable
接口
class MyThread implements Runnable {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(this.name + " --> " + i);
}
}
}
看到这里会发现,实现Runnable
接口与继承Thread
类的部分代码是基本上差不多的,没啥太大的区别。实际上也确实是这样。但是有一点区别很大的是,继承Thread
类会提供一个start()
方法用于开启线程,但是实现了Runnable
接口的话,这个start()
方法是没有的。
那么如何开启线程呢?还是需要Thread
类去开启线程。因为不管如何,开启线程都只能用Thread
类,区别就是使用什么样的方式罢了。
在Thread
中有一个构造方法,叫public Thread(Runnable target)
,这个方法接收的是Runnable
类型的对象,也就是我们自定义的线程类实现的类的类型。那么我们就可以利用这个构造方法去开启线程。
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();
}
那么,通过上面的方式,我们就可以开启线程了。
面试题:请解释
Thread
类与Runnable
接口实现多线程的区别
(其他问法:请解释多线程两种实现方式的区别):
Thread
类是Runnable
接口的子类,使用Runnable
接口实现多线程可以避免单继承局限;Runnable
接口实现的多线程可以比Thread
类实现的多线程更加清楚地描述数据共享的概念;
面试题:请写出多线程两种实现操作:
答案:把上面文章中列出的Thread
类继承的方式和Runnable
接口实现方式的代码都写出来。
实现Callable接口
Java 中开启线程的第三个方法就是实现Callable
接口,但是我们会发现在Thread
类中并没有直接支持Callable
接口的多线程方法。
但是从JDK1.5
开始,Java提供了FutureTack<V>
类,位于java.util.concurrent
包中,这个类主要负责的就是Callable
接口对象操作的。
在FutureTack<V>
这个类中有一个构造方法:public FutureTack(Callable<V> callable)
,专门接收Callable
接口对象。那么接收的目的就只有一个,就是取得call()
方法的返回值结果。
而另一方面,FutureTack
类也是Runnable
接口的子类,所以同样可以通过Thread
去调用开启。
那么我们可以上面第二个开启线程的方法的代码改为下面这样的:
public static void main(String[] arg) {
MyThread mt1 = new MyTread();
MyThread mt2 = new MyTread();
FutureTask<String> task1 = new FutureTask<String>(mt1);
FutureTask<String> task2 = new FutureTask<String>(mt2);
// FutureTask 是 Runnable 接口的子类,所以可以使用 Thread 类的构造来接收 task 对象
new Thread(task1).start();
new Thread(task2).start();
}