java学习线程篇之多线程的实现方式:继承Thread类、实现Runnable接口或Callable接口~

1. 基本概念:进程与线程

在操作系统的定义中,进程是指一次程序的完整运行,这个运行的过程之中内存、处理器、IO等资源操作都要为这个进程进行服务。

Windows属于多进程的操作系统,那么问题就来了?每一个进程都需要资源的支持,那么这么多进程怎么分配资源呢?

在同一个时间段上,会有多进程轮流去抢占资源,但是在某一个时间点上,只会有一个进程进行。

线程是在进程基础上进一步的划分结果,即:一个进程上可以同时创建多个线程。

线程是比进程更快的处理单元,而且所占的资源也小,那么多线程的应用也是性能最高的应用。

线程的存在离不开进程。进程如果消失,线程一定会消失;但是线程消失,进程未必消失。

2. 实现多线程的方式

在java中有三种多线程(JDK 1.5之后增加了第三种)。

2.1 继承Thread类

Thread类是一个支持多线程的一个功能类,只要有一个子类就可以实现多线程的支持

所有程序的起点是main方法,但是所有线程也一定要有一个自己的起点,那么在多线程的每个主体类之中都必须覆写Thread类中提供的run()方法

public void run(){}

这个方法没有返回值,那么也就表示线程一旦开始就要一直执行,不能够返回内容。

class MyThead extends Thread {  //这就是一个多线程的操作类
    private String name;

    public MyThead(String name) { //定义构造方法
        this.name = name;
    }

    @Override
    public void run(){ //覆写run方法,作为线程的主体操作方法
        for (int x = 0; x <5; x++) {
            System.out.println(this.name+"-->"+x);
        }
    }
}

    public class Test{
        public static void main(String[] args) {
            MyThead myThead1=new MyThead("线程1");
            MyThead myThead12=new MyThead("线程2");
            MyThead myThead3=new MyThead("线程3");
            myThead1.run();
            myThead12.run();
            myThead3.run();

        }
    }

以上代码的功能实现循环输出。所有的线程与进程一样,必须轮流的去抢占资源。所以多线程的执行是多个线程彼此交替执行,也就是说,如果直接调用了run方法,那么并不能够启动多线程。多线程唯一的启动方法就是Thread类中的start方法。

public void start() // 调用此方法实际上执行的是run方法

在这里插入图片描述
start()方法中调用本地的start0()方法,start0()中调用了run方法。

在这里插入图片描述

在这里插入图片描述

此时每一个线程对象交替执行,因此,要想启动线程必须调用start()方法。

疑问:为什么多线程启动不是调用run方法,而是调用start方法?

  • 首先,在Thread方法中存在一个异常抛出 IllegalThreadStateException(),本方法里的异常并没有用try catch处理,也没有在start()方法中用throws声明,因为此异常属于RuntimeException的子类,属于选择性处理。如果某一个线程对象重复进行启动,就会抛出此异常。
    在这里插入图片描述
    调用start0()方法,且此方法的结构与抽象方法类似,使用了native声明,在Java的开发里面有一门技术称为JNI技术(Java native Interface),这门技术的特点:使用java调用本机操作系统提供的函数,这样的技术有一个缺点,不能够离开特定的操作系统。

    如果要想线程能够执行,需要操作系统来进行资源分配,所以此操作严格来讲主要是由JVM根据不同的操作系统实现的。

    即:使用Thread类的start()方法不仅要启动多线程的执行代码,还要去根据不同的操作系统进行资源的分配。

在这里插入图片描述

2.2 实现Runnable接口

虽然Thread类可以实现多线程的主体类定义,但是它有一个问题,java具有单线程局限,正因如此针对于类的继承都应该是回避的问题,那么多线程也一样。为了解决单线程的限制,在java里面专门提供了Runnable接口,此接口定义如下:

在这里插入图片描述
与继承Thread类相比,实现Runnable接口的MyThread类在结构上与之前是没有区别的,但是有一点是有严重区别的,如果继承了Thread类,那么可以直接继承start()方法,但是实现Runnable接口是没有start()方法的。

不管何种情况下,如果要启动多线程一定要依靠Thread类完成。

在Thread类中有以下的构造方法:

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

接收Runnable接口对象,则可以利用其构造函数启动多线程。

class MyThead1 implements Runnable{  //这就是一个多线程的操作类
    private String name;

    public MyThead1(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 TestRun{
    public static void main(String[] args) {
        MyThead1 myThead1=new MyThead1("线程1");
        MyThead1 myThead2=new MyThead1("线程2");
        MyThead1 myThead3=new MyThead1("线程3");
        new Thread(myThead1).start();
        new Thread(myThead2).start();
        new Thread(myThead3).start();

    }
}


此时就避免了单继承局限,那么也就是说在实际工作中使用Runnable接口是最合适的

2.3 多线程两种实现方式的区别

首先一定要明确的是,使用Runnable接口与Thread类相比,解决了单继承的定义局限,所以不管后面的区别与联系是什么,至少这一点就下了死定义----如果要使用一定使用Runnable接口。

首先,观察一下Thread类定义,

在这里插入图片描述
发现Thread类实现了Runnable接口。那么这样一来,在这里插入图片描述
此时,整个的定义结构看起来非常像代理设计模式,如果是代理设计模式,客户端调用的代理类的方法也应该是接口里提供的方法,那么也应该是run()才对。

还有一点,使用Runnable接口可以比Thread类能够更好地描述出数据共享这一概念。此时的数据共享指的是多个线程访问同一资源的操作。

每一个线程对象都必须通过start()启动。

class MyThead extends Thread {  //这就是一个多线程的操作类
    private int price=10;
    private String name;

    public MyThead(String name) { //定义构造方法
        this.name = name;
    }

    @Override
    public void run(){ //覆写run方法,作为线程的主体操作方法
        while (this.price>0) {
            System.out.println(this.name+": 降价后的价格: "+this.price--);
        }
    }
}

    public class TestThr{
        public static void main(String[] args) {
            MyThead myThead1=new MyThead("线程1");
            MyThead myThead12=new MyThead("线程2");
            MyThead myThead3=new MyThead("线程3");
            myThead1.start();
            myThead12.start();
            myThead3.start();

        }
    }


在这里插入图片描述
三个线程对象各自占用内存空间,并不存在数据共享的这一概念。

class MyThead implements Runnable {  //这就是一个多线程的操作类
    private int price=10;
    private String name;

    public MyThead(String name) { //定义构造方法
        this.name = name;
    }

    @Override
    public void run(){ //覆写run方法,作为线程的主体操作方法
        while (this.price>0) {
            System.out.println(this.name+": 降价后的价格: "+this.price--);
        }
    }
}

    public class TestThr{
        public static void main(String[] args) {
            MyThead myThead1=new MyThead("线程1");
            MyThead myThead2=new MyThead("线程2");
            MyThead myThead3=new MyThead("线程3");
            //myThead1.start();
           // myThead12.start();
            //myThead3.start();
            new Thread(myThead1).start();
            new Thread(myThead2).start();
            new Thread(myThead3).start();
        }
    }


但是在实现Runnable接口时,三个线程对象都直接占用同一个MyThread类的对象引用,也就是说这三个线程对象都直接访问同一个数据资源。

面试题:请解释Thread类和Runnable接口实现多线程的区别?(请解释多线程两种实现方式的区别?)

  1. Thread类是Runnable接口的子类,使用Runnable接口实现多线程可以避免单继承局限。
  2. Runnable接口实现的多线程可以比Thread类实现的多线程更加清楚的描述数据共享的概念。

面试题:请写出多线程两种实现操作。

把Thread类和Runnable接口实现方式的代码都写出来。

2.4 第三种实现方式(理解)

使用Runnable接口实现的多线程可以避免单继承局限,的确很好,但是Runnable接口里面的run()方法不能返回操作结果。为了解决这样的矛盾,提供了一个新的接口,即Callable接口

在这里插入图片描述
Thread类里面并没有直接支持Callable接口的多线程应用。

从JDK1.5开始提供有public class FutureTask<V> implements RunnableFuture<V>这个类负责Callable接口对象操作。这个接口实现了RunnableFuture接口。

FutureTask类中的构造方法:
在这里插入图片描述

接收的目的只有一个,那么就是取得的call()方法的返回结果。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyThread implements Callable<String> {
    private int price = 10;

    @Override
    public String call() throws Exception {
        for (int x = 0; x <100; x++) {
            System.out.println("价格: "+this.price--);
        }
        return "不能再降价了";
    }
}

public class TestCal {
    public static void main(String[] args) throws Exception {
        MyThread myThead = new MyThread();
        MyThread myThead1 = new MyThread();
        FutureTask<String> task=new FutureTask<String>(myThead);
        FutureTask<String> task1=new FutureTask<String>(myThead1);
        new Thread(task).start();
        new Thread(task1).start();
        System.out.println(task.get());
        System.out.println(task1.get());
    }
}

在这里插入图片描述

最麻烦的问题在于需要接收返回值信息,并且要与原始的多线程的实现靠拢(Thread类)。

2.5 小结

  1. 对于多线程的实现,重点在于Runnable接口与Thread类启动的配合上;
  2. 对于JDK 1.5新特性,了解就行,知道区别在于返回结果上。

版权声明:本文为qq_34474324原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。