JAVA中的多线程之Thread,Runnable和Callable

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类来说有如下优势:

  1. 适合多个相同程序代码的线程去处理同一资源的情况
  2. 可以避免由于Java单继承特性所带来的局限
  3. 增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。

所以在开发中建议多用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()方法启动多线程

猜你喜欢

转载自blog.csdn.net/weixin_43938560/article/details/89683888