在JAVA中,有两种方式可以实现多线程。
1、继承Thread类;
2、实现Runnable(Callable)接口。
JDK从最开始定义多线程支持时,只有两种实现要求:要么继承Thread类,要么实现Runnable接口,但是在JDK1.5开始又提供了一个新的线程接口:Callable接口。
继承Thread实现多线程:
java.long.Thread类是一个负责线程操作的类,任何类只需要继承Thread类就可以成为一个线程 的主类。但是既然是主类就必须有它的使用方法,而线程启动的主方法需要覆写Thread类中的run()方法实现,线程主体类的定义格式如下:
class 类名称 extends Thread{//继承Thread类
属性... ; //类中定义属性
方法... ; //类中定义方法
public void run(){ //覆写Thread类中的run()方法,此方法是线程的主体
线程主体方法 ;
}
}
定义一个线程操作类:
class MyThread extends Thread{//这是一个多线程的操作类
private String name ; //定义类中的属性
public MyThread(String name ){//定义构造方法
this.name = name ;
}
@Override
public void run(){//覆写run()方法,作为线程的主操作方法
for(int i = 0 ; i < 200 ; i ++){
System.out.println(this.name + "---->" + x) ;
}
}
}
本程序线程的主要功能是实现循环的输出操作,所有的线程与进程都是一样的,必须轮流去抢占资源,所以多线程的执行应该是多个线程彼此交替执行。也就是说,直接调用run()方法,并不能启动多线程,多线程的唯一启动方法就是Thread类中的start()方法:public void start()(调用此方法执行方法体是run()方法定义的代码)。
启动多线程:
public class Demo{
public static void main (String [] args){
MyThread mt1 = new Mythread("线程1") ;//实例化多线程类对象
MyThread mt2 = new Mythread("线程2") ;
MyThread mt3 = new Mythread("线程3") ;
mt1.start() ;//启动多线程
mt2.start() ;
mt3.start() ;
}
}
疑问:为什么多线程启动不是调用run()方法而是调用start()??
为了解释多线程的启动调用的问题,可以查看java.long.Thread类的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) {
}
}
}
private native void start0();
可以发现在start()方法里面要调用一个start0()方法,而且此方法的结构与抽象方法类似,使用了native声明。
在JAVA的开发中有一门技术被称为JNI(Java Native Interface)技术,这门技术的特点是使用Java调用本机操作系统提供的函数。但是此技术有一个缺点,就是离不开特定的操作系统。如果想要执行线程,需要操作系统来进行资源分配,所以此操作严格来主要是由JVM负责根据不同的操作系统而实现的。即使用Thread类的start()方法不仅要启动多线程的执行代码,还要根据不同的操作系统进行资源的分配。
另外,通过上述代码可以发现,在Thread类的start()方法中会抛出一个“IllegalThreadStateException”异常,本方法里面使用了throw抛出异常,本应该用try...catch...捕获异常,或者在start()方法是使用throws声明,但是此处并没有这样做,因为此异常是继承自“RuntimeException”,这样就可以由用户选择性进行性处理,如果某一个线程兑现进行了重复启动(同一个线程对象调用多次start()方法),就会抛出此异常。
实现Runnable接口:
使用Thread类的确可以方便的进行多线程的实现,但是这种方式最大的缺点就是单继承局限性,为此,在Java中也可以利用Runnable接口来实现多线程。而这个接口的定义如下:
@FunctionalInterface
interface Runnable{
public void run() ;
}
在Runnable接口也定义了run()方法,所以只需要覆写run()方法即可。
使用Runnable接口实现多线程:
class MyThread extends implements Runnable{//这是一个多线程的操作类
private String name ; //定义类中的属性
public MyThread(String name ){//定义构造方法
this.name = name ;
}
@Override
public void run(){//覆写run()方法,作为线程的主操作方法
for(int i = 0 ; i < 200 ; i ++){
System.out.println(this.name + "---->" + x) ;
}
}
}
本程序实现了Runnable接口,也正常的覆写了run()方法,但是却会存在一个问题:要启动多线程,必须调用Thread类中的start()方法,如果继承了Thread类,那么可以直接使用Thread类中的start()方法,但是Runnable接口并没有提供可以被继承的start()方法,这是该如何启动多线程呢?
通过观察Thread类可以发现,Thread类中有一个构造方法:public Thread(Runnable target),此方法可以接收一个Runnable接口对象。
利用Thread类启动多线程:
public class Demo{
public static void main (String [] args){
MyThread mt1 = new Mythread("线程1") ;//实例化多线程类对象
MyThread mt2 = new Mythread("线程2") ;
MyThread mt3 = new Mythread("线程3") ;
new Thread(mt1).start() ;//启动多线程
new Thread(mt2).start() ;
new Thread(mt3).start() ;
}
}
本程序首先利用Thread类的对象包装了Runnable接口对象实例,然后利用了Thread类中的start()方法启动多线程。
因为在Runnable接口的声明处使用了@FunctionalInterface的注解,证明Runnable接口是一个函数式接口,那么我们就可以使用Lambda表达式的风格编写:
public class Demo{
public static void main (String [] args){
String name = "线程对象" ;
new Thread(()->{
for(int x = 0 ; x < 200 ; x ++){
System.out.println(name + " - - >" + x ) ;
}
}).start() ;
}
}
本程序利用Lambda表达式直接定义的线程主体实现操作,并且依然依靠Thread类的start()方法进行启动,这样的做法比直接用Runnable接口更加方便。
使用Runnable接口可以有效的避免单继承局限问题,所以在实际开发中,对于多线程的实现首选的就是Runnable接口。
面试题:请解释Thread类与Runnable接口实现多线程的区别?(请解释两种多线程实现方式的区别)
1、Thread类是Runnable接口的子类,使用Runnable接口实现多线程可以避免单继承的局限性。
2、Runnable接口实现的多线程可以比Thread类实现的多线程更好的描述数据共享这一概念!