多线程的实现方式有三种,哪种更好

在Java中,实现多线程的方式有3种,分别是继承Thread类、实现Runnable接口、实现Callable接口。关于它们是如何实现,有何区别,且听我慢慢道来。

方式一:继承Thread类

一个普通类只要继承了传说中的Thread类,那么,这个普通类就具备了多线程操作能力。

But,这个普通类必须重写Thread类中的run()方法,因为我们所编写的代码要放在这个方法中,而且所编写的代码是一个线程体。

通过继承Thread类实现多线程实例:

MyThread类(普通类)

package cn.tkr.thread;

public class MyThread extends Thread {

    //重写Thread类中的run()方法
    @Override
    public void run() {
        //编写线程体(线程要执行的代码)
        System.out.println("大家好,我是MyThread类中的run()方法中的代码");
    }
}

TestMyThread类(测试类)

package cn.tkr.thread;

public class TestMyThread {
    public static void main(String[] args) {
        MyThread m = new MyThread();  //创建线程类的对象
        m.start();  //启动线程
        
        System.out.println("我是main方法中的代码");
    }
}

运行结果:

大家好,我是MyThread类中的run()方法中的代码
我是main方法中的代码

实例分析:

在运行这个程序时,完美的启动了Java虚拟机,负责执行主线程(主方法)中的代码,在执行MyThread m = new MyThread()之前有一个线程,线程名称为main,是主线程,我们使用m.start()启动线程,这个时候就有两个线程了,一个是主线程,一个是m线程。

要注意一点,由于哪个线程先“抢占”到CPU资源不确定,所以这两个线程的哪个先执行也是不确定的。

方式二:实现Runnable接口

一个普通类实现了Runnable接口,那么,这个普通类也就具备了多线程操作能力。

But,Runnable接口中没有定义start()方法,所以想要启动线程,我们还得拜托一下Thread类,毕竟人家有start()方法。

通过实现Runnable接口实现多线程实例:

MyRunnable类(普通类)

package cn.tkr.thread;

public class MyRunnable implements Runnable {

    //重写Runnable接口中的run()方法
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println("我是MyRunnable类中的run方法中的代码" + i);
        }
    }
}

TestMyRunnable类(测试类)

package cn.tkr.thread;

public class TestMyRunnable {
    public static void main(String[] args) {

        MyRunnable mr = new MyRunnable();   //创建线程类的对象
        Thread m = new Thread(mr);
        m.start();

        for (int i = 0; i < 3; i++) {
            System.out.println("我是main方法中的代码" + i);
        }
    }
}

运行结果:

我是main方法中的代码0
我是MyRunnable类中的run方法中的代码0
我是MyRunnable类中的run方法中的代码1
我是MyRunnable类中的run方法中的代码2
我是main方法中的代码1
我是main方法中的代码2

实例分析:

在Thread类中有个构造方法叫Thread(Runable target ),专门用来接收线程类的对象(实现Runnable接口的线程类对象),把mr线程传递给Thread类之后,我们就可以通过对象m来完美地启动线程。

方式三:实现Callable接口

JDK1.5之后,冒出了一个叫Callable的接口,它的到来,让Runnable抑郁了,因为Callable太强大了。

与Runnable实现多线程相比,Callable支持泛型,call()方法可以有返回值,而且还支持泛型的返回值,比run()方法更强大的一点是,居然还能抛出异常。

But,Callable接口中的Call()方法需要借助FutureTask类获取结果。

FutureTask类是RunnableFuture接口的实现类,而RunnableFuture接口又继承了Future接口和Runnable接口,所以FutureTask类也是Runnable接口的实现类。

注:FutureTask类是一个任务管理器类

切,绕了这么一大圈,到头来,不还是要靠我们家Runnable接口嘛。

总的来说,这种方式是通过创建FutureTask类的对象将Callable接口的实现类传入,从而实现多线程。

通过实现Callable接口实现多线程实例:

RandomCallable类(普通类)

package cn.tkr.thread;

import java.util.concurrent.Callable;

public class RandomCallable implements Callable<String> {
    @Override
    public String call() throws Exception {

        String[] str = {"A","B","C","D","E"};   //创建一个数组,长度为5
        int random = (int)(Math.random()*4)+1;  //产生一个1~4之间的随机数
        return str[random]; //根据产生的随机数返回数组中对应位置的字符串
    }
}

TestRandomCallable类(测试类)

package cn.tkr.thread;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TestRandomCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        RandomCallable rc = new RandomCallable();   //创建一个任务
        FutureTask<String> ft = new FutureTask<>(rc);   //创建一个任务管理器

        Thread t = new Thread(ft);

        System.out.println("任务是否已完成:" + ft.isDone());

        t.start();
        System.out.println(ft.get());

        System.out.println("任务是否已完成:" + ft.isDone());
    }
}

运行结果:

任务是否已完成:false
B
任务是否已完成:true

实例分析:

FutureTask类是Runnable接口的实现类,所以要启动线程我们还是要请Thread类来帮忙。

在使用start()方法之前,任务是没有完成的,所以isDone()的结果为false,当我们开启线程后,用get()方法获取到了结果,说明任务已完成,因为只有结果出来任务才能结束,否则无论get()方法后面有多少句代码都不会执行的。

继承Thread类与实现Runnable接口的区别

大家都知道,Java类具有单继承的特点,如果一个类选择继承Thread类来实现多线程,那么这个类就不能再继承其他类了。

但是如果选择实现Runnable接口,不仅避免了单继承带来的局限性,还能方便共享资源,同一个资源可以有多个代理访问。

实例(三个窗口卖票问题):

在火车站的售票厅里,有三个窗口在卖5张票,每个窗口有30人在排队,那到底要使用哪种方式实现多线程呢?

第一种,使用继承Thread类的方式来实现

实例:

TicketThread类

package cn.tkr.thread;

public class TicketThread extends Thread {

    private int tickets = 5;

    public TicketThread (String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            if (tickets > 0){
                System.out.println(super.getName() + "卖第" + (tickets--) + "张票");
            }
        }
    }
}

TestTicketThread类(测试类)

package cn.tkr.thread;

public class TestTicketThread {
    public static void main(String[] args) {

        //创建三个线程类的对象
        TicketThread t1 = new TicketThread("窗口一");
        TicketThread t2 = new TicketThread("窗口二");
        TicketThread t3 = new TicketThread("窗口三");

        t1.start();
        t2.start();
        t3.start();

    }
}

运行结果:

窗口二卖第5张票
窗口三卖第5张票
窗口一卖第5张票
窗口三卖第4张票
窗口二卖第4张票
窗口三卖第3张票
窗口一卖第4张票
窗口三卖第2张票
窗口二卖第3张票
窗口三卖第1张票
窗口一卖第3张票
窗口一卖第2张票
窗口二卖第2张票
窗口一卖第1张票
窗口二卖第1张票

实例分析:

三个窗口同时卖票,我们就要创建三个线程。从运行结果看,本来只卖5张票,现在变成了15张了,很显然,private int tickets = 5这个资源并没有共享。

这三个不同的线程类的对象,所用到的资源都是相互独立的,也就是说线程类对象t1、t2、t3用到的都是private int tickets = 5这个资源,所以就会出现15张票。

再看另一种,使用实现Runnable接口的方式来实现

实例:

TicketRunnable类

package cn.tkr.thread;

public class TicketRunnable implements Runnable {

    private int tickets = 5;

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            if (tickets > 0){
                System.out.println(Thread.currentThread().getName() + "卖第" + (tickets--) + "张票");
            }
        }
    }
}

TestTicketRunnable类(测试类)

package cn.tkr.thread;

public class TestTicketRunnable {
    public static void main(String[] args) {

        //创建线程类的对象
        TicketRunnable tr = new TicketRunnable();

        Thread t1 = new Thread(tr,"窗口一");
        Thread t2 = new Thread(tr,"窗口二");
        Thread t3 = new Thread(tr,"窗口三");

        t1.start();
        t2.start();
        t3.start();

    }
}

运行结果:

窗口一卖第5张票
窗口二卖第4张票
窗口一卖第3张票
窗口二卖第2张票
窗口一卖第1张票

实例分析:

通过实现Runnable接口的方式实现多线程,达到了多个线程共享同一个资源的目的,完美地解决了使用Thread类来实现多线程出现的问题。

总结

以上是我分享给大家的关于多线程实现方式的一些总结。如果觉得还不错的话,就送我一个赞吧!如果本文对你有用的话,也欢迎收藏哦!

猜你喜欢

转载自blog.csdn.net/m0_47890251/article/details/107006836
今日推荐