既然直接继承 Thread 类和实现 Runnable 接口都能实现多线程,那么这两种实现多线程的方式在实际应用中又有什么区别呢?接下来通过一种应用场景来分析。

    假设售票厅有 4 个窗口可发售某日某次列车的 100 张车票,这时,100 张车票可以看作共享资源,4 个窗口需要创建 4 个线程。为了更直观显示窗口的售票情况,可以通过 Thread 的 currentThread()方法得到**当前的线程的实例对象**,然后调用 getName()方法可以获取到当前线程的名称。
    **
    首先通过继承 Thread 类的方式来实现多线程的创建,如下所示。

    1. public class example04 {
    2. public static void main(String[] args) {
    3. //创建线程对象 TicketWindow 并开启
    4. new TicketWinodw().start();
    5. new TicketWinodw().start();
    6. new TicketWinodw().start();
    7. new TicketWinodw().start();
    8. }
    9. }
    10. class TicketWinodw extends Thread{
    11. private int tickets = 100;
    12. @Override
    13. public void run() {
    14. super.run();
    15. while (true) { //通过死循环语句打印语句
    16. if (tickets > 0) {
    17. Thread thread = Thread.currentThread(); //获取当前线程
    18. String thread_name = thread.getName(); //获取当前线程的名称
    19. System.out.println(thread_name + "正在发售第 " + tickets-- + "张票");
    20. }
    21. }
    22. }
    23. }

    运行结果如下所示。
    QQ截图20200708162736.png
    从上图所示的运行结果可以看出,每张票都被打印了 4 次。出现这样现象的原因是 4 个线程没有共享 100 张票,而是各自出售了 100 张票。在程序中创建了 4 个TicketWindow 对象,就等于创建了 4 个售票程序,每个程序中都有 100 张票,每个线程在独立地处理各自的资源。需要注意的是,上述代码块中每个线程都有自己的名字,主线程默认的名字是“main”,用户创建的第 1 个线程的名字默认为“Thread-0”,第 2 个线程的名字默认为“Thread-1”,以此类推。如果希望指定线程的名称,可以调用 setName(String name)方法为线程设置名称

    由于现实中铁路系统的票资源是共享的,因此上面的运行结果显然不合理。为了保证资源共享,在程序中**创建一个售票对象,然后开启多个线程去运行同一个售票对象售票**方法简单来说就是 4 个线程运行同一个售票程序,这时就需要用到多线程的第 2 种实现方式

    接下来,通过实现 Runnable 接口的方式来实现多线程的创建。对上述代码块进行修改,并使用构造方法 Thread(Runnable target, Strign name)在创建线程对象的同时指定线程的名称,如下所示。

    1. public class example05 {
    2. public static void main(String[] args) {
    3. //创建线程对象 TicketWindow 并开启
    4. TicketWinodw task = new TicketWinodw();
    5. new Thread(task, "窗口1").start();
    6. new Thread(task, "窗口2").start();
    7. new Thread(task, "窗口3").start();
    8. new Thread(task, "窗口4").start();
    9. }
    10. }
    11. class TicketWinodw implements Runnable{
    12. private int tickets = 100;
    13. @Override
    14. public void run() {
    15. while (true) { //通过死循环语句打印语句
    16. if (tickets > 0) {
    17. Thread thread = Thread.currentThread(); //获取当前线程
    18. String thread_name = thread.getName(); //获取当前线程的名称
    19. System.out.println(thread_name + " 正在发售第 " + tickets-- + " 张票");
    20. }
    21. }
    22. }
    23. }

    运行结果如下所示。
    QQ截图20200708191114.png
    上述代码块只创建了一个 TicketWindow 对象,然后创建了 4 个线程,在每个线程上都去调用这个 TicketWindow 对象中的 run()方法,这样就可以确保 4 个线程访问的是同一个 tickets 变量,共享 100 张车票。

    通过上面的两个文件可以看出,实现 Runnable 接口相对于继承 Thread 来说,有如下显着的好处。

    1. 适合多个相同程序代码的线程去处理同一个资源的情况,把线程与程序代码、数据有效的分离,很好的体现了面对对象的设计思想。
    2. 可以避免由于 Java 的单继承带来的局限性。在开发中经常碰到这样一种情况,就是使用一个已经继承了某一个子类创建线程,由于一个类不能同时有两个父类,所以不能用继承 Thread 类的方式,那么久只能采用实现 Runnable 接口的方式

    事实上,大部分的多线程应用都会采用第 2 中方式,即实现 Runnable 接口。