学科分类
目录
Java基础

三种实现多线程方式的对比分析

多线程的实现方式有三种,其中Runnable接口和Callable接口实现多线程的方式基本相同,主要区别就是Callable接口中的方法有返回值,并且可以声明抛出异常。那么通过继承Thread类和其他两种接口实现多线程的方式有什么区别呢?接下来通过一个应用场景来分析说明。

假设售票厅有四个窗口可发售某日某次列车的100张车票,这100张车票就可以看做共享资源,四个售票窗口相当于四个线程,为了更直观显示窗口的售票情况,可以通过Thread的currentThread()方法得到当前线程的实例对象,然后调用getName()方法获取到线程名称。接下来,通过继承Thread类的方式来实现多线程,如文件1所示。

文件1 Example04.java

 1    // 1、定义一个继承Thread线程类的子类
 2    class TicketWindow extends Thread {
 3        private int tickets = 100;
 4        public void run() {
 5            while (true) { 
 6                if (tickets > 0) {
 7                    System.out.println(Thread.currentThread().getName()
 8                                         + " 正在发售第 " + tickets-- + " 张票 ");
 9                }
 10            }
 11        }
 12    }
 13    public class Example04 {
 14        public static void main(String[] args) {
 15            // 分别创建4个线程对象TicketWindow并开启,来模拟4个售票窗口
 16            new TicketWindow().start(); 
 17            new TicketWindow().start(); 
 18            new TicketWindow().start(); 
 19            new TicketWindow().start(); 
 20        }
 21    }

运行结果如图1所示。

图1 运行结果

从图1可以看出,每张票都被发售了四次。出现这种现象的原因是:四个线程没有共享100张票,而是各自出售了100张票。在程序中创建了四个TicketWindow对象,就等于创建了四个售票程序,而每个TicketWindow对象中都有一个tickets票据变量,这样每个线程在执行任务时都会独立地处理各自的资源,而不是共同处理同一个售票资源。

小提示:

文件1中创建并启动4个线程时并没有指定线程的名称,但运行结果却显示有Thread-0、Thread-1...这样的线程名称。这是因为,在创建多线程时如果没有通过构造方法指定线程名称,则系统默认生成线程名称。

由于现实中,售票系统中的票资源是共享的,因此上面的运行结果显然不合理。为了保证售票资源共享,在程序中只能创建一个售票对象,然后开启多个线程去共享同一个售票对象来执行售票方法,简单来说就是四个线程运行同一个售票程序,这时就需要通过实现Runnable接口的方式来实现多线程。

接下来将文件1进行修改,并使用构造方法Thread(Runnable target, String name)在创建线程对象的同时指定线程的名称,如文件2所示。

文件2 Example05.java

 1    // 定义一个实现Runnable接口的实现类
 2    class TicketWindow2 implements Runnable {
 3        private int tickets = 100;
 4        public void run() {
 5            while (true) {
 6                if (tickets > 0) {
 7                    System.out.println(Thread.currentThread().getName()
 8                                         + " 正在发售第 " + tickets-- + " 张票 ");
 9                }
 10            }
 11        }
 12    }
 13    public class Example05 {
 14        public static void main(String[] args) {
 15            // 创建TicketWindow实例对象tw
 16            TicketWindow2 tw = new TicketWindow2(); 
 17            // 分别创建4个线程对象同时进行命名,并开启线程
 18            new Thread(tw, "窗口1").start();      
 19            new Thread(tw, "窗口2").start();      
 20            new Thread(tw, "窗口3").start();     
 21            new Thread(tw, "窗口4").start();      
 22        }
 23    }

运行结果如图2所示。

图2 运行结果

文件2中,通过实现Runnable接口的方式只创建了一个TicketWindow2对象,然后创建了四个线程,在每个线程上都调用同一个TicketWindow2对象中的run()方法,这样就可以确保四个线程访问的是同一个tickets变量,共享100张车票。

通过上面的售票案例,并结合实际情况进行分析,通过实现Runnable接口(或者Callable接口)相对于继承Thread类实现多线程来说,有如下显著的好处:

(1)适合多个线程去处理同一个共享资源的情况,把线程同程序代码、数据有效的分离,很好的体现了面向对象的设计思想。

(2)可以避免Java单继承带来的局限性,由于一个类不能同时有两个父类,所以在当前类已经有一个父类的基础上,那么就只能采用实现Runnable接口或者Callable接口的方式来实现多线程。

事实上,实际开发中大部分的多线程应用都会采用Runnable接口或者Callable接口的方式实现多线程。

点击此处
隐藏目录