线程创建两种方式的比较

Author Avatar
WincerChan 6月 12, 2017
  • 在其它设备中阅读本文章

线程创建的两种方式

继承 Thread 类

class DemoThread extends Thread{
  ......
    @Override
    public void run(){
      ......
    }
}
DemoThread dt = new DemoThread();
dt.start();

实现 Runnable 接口

class DemoRunnable implements Runnable{
  ......
    @Override
    public void run(){
      ......
    }
}
DemoRunnable dr = new DemoRunnable();
Thread td = new Thread(dr);
td.start();

我们知道,在 Java 中是单继承机制,即一个子类只能继承自一个父类,故采用继承 Thread 类的方法会有缺陷,而 Runnable 的资源可以被多个线程所共享,适合多个线程处理同一资源的情况,接下来通过一个具体的例子来讲解一下:

模拟火车站售票

现在还剩下 5 张火车票,共有 3 个售票窗口售卖这 5 张票,我们看看分别用两种方式来模拟会出现什么结果

Thread 模拟

public class DemoThread extends Thread{
    private int tickets = 5;        //火车票总数
    private String name;            //窗口(线程)名字
    public DemoThread(String name){
        this.name = name;
    }
    public void run(){
        while(tickets > 0){
            tickets --;        //票还有富余,卖掉一张
            System.out.println(name+
                               "出票1张,剩余票数"+ tickets);
        }
    }

    public static void main(String[] args){
        //创建3个线程,模拟三个窗口
        DemoThread dt1 = new DemoThread("窗口1");
        DemoThread dt2 = new DemoThread("窗口2");
        DemoThread dt3 = new DemoThread("窗口3");
        //进程启动
        dt1.start();
        dt2.start();
        dt3.start();
    }
}

首先看看 Thread 的运行结果:

窗口 1 出票 1 张,剩余票数 4
窗口 1 出票 1 张,剩余票数 3
窗口 1 出票 1 张,剩余票数 2
窗口 1 出票 1 张,剩余票数 1
窗口 1 出票 1 张,剩余票数 0
窗口 2 出票 1 张,剩余票数 4
窗口 2 出票 1 张,剩余票数 3
窗口 2 出票 1 张,剩余票数 2
窗口 3 出票 1 张,剩余票数 4
窗口 2 出票 1 张,剩余票数 1
窗口 3 出票 1 张,剩余票数 3
窗口 3 出票 1 张,剩余票数 2
窗口 3 出票 1 张,剩余票数 1
窗口 3 出票 1 张,剩余票数 0
窗口 2 出票 1 张,剩余票数 0

分析一下这个结果:窗口 1 抢占到了 CPU,卖了 5 张票,随后窗口 2 和窗口 3 居然也卖了 5 张票,也就是说这三个窗口一共卖了 15 张票,这显然不是我们需要的结果

Runnable 模拟

public class DemoRunnable implements Runnable{
    private int tickets = 5;

    public void run(){
        while(tickets > 0){
            tickets --;         //票数有富余,出票1张
            System.out.println(Thread.currentThread().getName()+
                               "出票,剩余票数"+ -- tickets);
        }
    }

    public static void main(String[] args){
        DemoRunnable dr = new DemoRunnable();
        Thread t1 = new Thread(dr, "窗口1");
        Thread t2 = new Thread(dr, "窗口2");
        Thread t3 = new Thread(dr, "窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

接下来看看 Runnable 的运行结果:

窗口 1 出票,剩余票数 4
窗口 3 出票,剩余票数 2
窗口 3 出票,剩余票数 0
窗口 2 出票,剩余票数 3
窗口 1 出票,剩余票数 1

这就正常了,三个窗口一共售卖了 5 张票,但是我们看出票的顺序:4-2-0-3-1,为什么不是 4-3-2-1-0 呢?

这是因为 Java 的线程的执行时机是不确定的,我们分析一下结果:

  1. t1 首先获得了 CPU 的资源:窗口 1 开始出票,并打印剩余票数 4,让出资源;
  2. t2 其次获得了 CPU 的资源:窗口 2 开始出票,此时剩余票数为 3,在准备打印剩余票数的时候出现了一点小问题;
  3. t3 出现并抢占 CPU 的资源:在窗口 2 出了票但是还没打印结果的时候,这个时候 t3 开始出票,并且打印出剩余票数 2;
  4. t1 再次获得了 CPU 的资源:在窗口 1 出了票,在准备打印出剩余票数 1 的时候,又被窗口 3 抢得了资源;
  5. t3 再次获得了 CPU 的资源:在窗口 3 出了票并打印出剩余票数 0,让出资源;
  6. t2 终于获得了资源,打印出剩余票数 3,让出资源;
  7. t1 最后获得了资源,开始打印出剩余票数 1,结束;

总结

在程序开发中只要是多线程肯定永远以实现 Runnable 接口为主,因为实现 Runnable 接口比继承 Thread 类有如下好处:

  1. 避免单继承的局限一个类可以继承多个接口;
  2. 适合于资源的共享。

本文标题: 线程创建两种方式的比较
最后更新:2017 年 12 月 24 日 - 20:12
本文链接:https://blog.itswincer.com/posts/c10dafe5/
本文采用:署名-非商业性使用-禁止演绎 4.0 国际 协议进行许可,阅读 相关说明