JAVA中线程的实现
线程的实现主要通过三种方式
1. 通过继承Thread类
一个类只要继承了Thread类,那么这个类就可以称之为多线程类,在Thread的子类中必须明确的覆写Thread类的run()方法,此方法称之为线程主体。
但是要启动线程并不能直接调用run()方法,而是要调用从Thread类中继承来的start()方法来启动线程。
在使用了线程后,哪一个线程对象先抢到了CPU资源,哪个线程对象就先抢先运行
如果一个类通过继承Thread类来实现多线程,那么只能调用一次start()方法,如果多次调用start()方法则会抛出IllegalThreadStateException异常
2. 通过实现Runnable接口
在Runnable接口中只提供有一个 抽象方法run()
Thread也是实现了Runnable接口
实现多线程时,如果是继承了Thread类,要启动多线程那么可以直接调用start()方法,但是现在是实现的Runnable接口,那么如何来启动多线程呢?
其实对于实现Runnable的类,也是通过Thread来完成多线程的启动
在Thread类中提供了两个构造方法,一个是public Thread(Runnable target)。另外一个是public Thread(Runnable target,String name),这两个构造方法都可以接收Runnable的子类实例对象,所以可以依靠此点来启动多线程
Thread类和Runnable的区别
其实Thread也是Runnable接口的子类,但是Thread类和Runnable接口在使用上是有区别的:如果一个类继承Thread类,则不适合于多个线程共享资源,而实现了Runnable接口,就可以方便地实现资源共享
继承Thread的类不能资源共享
package Thread_;
public class TestThread extends Thread{
private int ticket = 5;
public void run(){
for(int i = 0;i < 100;i++){
if(ticket > 0){
System.out.println("卖票: ticket" + ticket--);
}
}
}
public static void main(String[] args){
TestThread tt1 = new TestThread();
TestThread tt2 = new TestThread();
TestThread tt3 = new TestThread();
tt1.start();
tt2.start();
tt3.start();
}
}
一种实验结果如下:
卖票: ticket5
卖票: ticket5
卖票: ticket4
卖票: ticket3
卖票: ticket2
卖票: ticket1
卖票: ticket5
卖票: ticket4
卖票: ticket3
卖票: ticket2
卖票: ticket1
卖票: ticket4
卖票: ticket3
卖票: ticket2
卖票: ticket1
这里3个Thread对象启动了3个线程,3个线程分别卖出了5张票,没有达到资源共享的目的
实现Runnable接口的类可以资源共享
package Thread_;
public class TestThread implements Runnable{
private int ticket = 5;
public void run(){
for(int i = 0;i < 100;i++){
if(ticket > 0){
System.out.println("卖票: ticket" + ticket--);
}
}
}
public static void main(String[] args){
TestThread tt1 = new TestThread();
new Thread(tt1).start();
new Thread(tt1).start();
new Thread(tt1).start();
}
}
一种实验结果如下
卖票: ticket5
卖票: ticket4
卖票: ticket2
卖票: ticket1
卖票: ticket3
这里虽然启动了三个线程,但是3个线程卖的的同一份的5张票,ticket的属性是被共享的
造成这种情况的原因在于在Thread的子类TestThread中,创建的3个线程是3个TestThread对象,每个对象有自己的5张票,而在Runnable的子类中只是创建了一个TestThread对象,所以不论后面通过多少Thread类的对象来启动TestThread对象的线程时,都是启动的是同一个对象的线程,而这一个对象只有5张票
在Thread子类中3个对象分别使用start来启动自己的线程,而在Runnable的子类中,new了3个Thread类的匿名对象,通过这3个Thread对象来启动一个TestThread对象,其实这3个Thread匿名对象都指向的是同一个TestThread对象,启动线程时也启动的是同一个对象的线程,所以Runnable接口的子类可以实现资源共享
3.实现Runnable接口相比于继承Thread类来说的优势
实现Runnable接口相比于继承Thread类来说有如下优势:
- 适合多个相同程序代码的线程去处理同一资源的情况
- 可以避免由于Java单继承特性所带来的局限
- 增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。
所以在开发中建议多用Runnable接口实现多线程
4.通过实现Callable< V >接口
对于Thread和Runnable实现多线程,这两种实现方式都会出现不能返回操作结果的问题,为了解决这个问题就有了Callable< V >接口,Callable< V >接口中的public V call() throws Exception;方法可以返回线程操作的结果
import java.util.concurrent.Callable;
class Mythread implements Callable<String>{
private int ticket = 5;
public String call() throws Exception{
for(int i = 0;i < 100;i++){
if(ticket > 0){
System.out.println("卖票: ticket" + ticket--);
}
}
return "票已经卖光!";
}
}
由于Callable< V >接口是不Runnable接口的子接口,所以无法用Thread的对象来接收Callable< V >的实例,所以无法启动多线程
为了解决这样的情况,于是出现了FutureTask< V >类,FutureTask类的构造方法可以接收Callable< V >接口的实例对象,而FutureTask< V >类是Runnable接口的子类,所以Thread的实例对象可以接收FutureTask< V >的实例对象,于是通过两次传递,完成了对Callable< V >对象多线程的启动
Callable< V >接口的子类利用FutureTask类是实现包装,又由于FutureTask是Runnable接口的子类,所以可以利用Thread类的start()方法来启动多线程,执行完毕之后Future接口中的get()方法启动多线程