Java 多线程:Thread 和 Runnable 区别

本文内容大多基于官方文档和网上前辈经验总结,经过个人实践加以整理积累,仅供参考。


继承 java.lang.Thread 类和实现 java.lang.Runnable 接口是 Java 实现线程的两种方法,通常以实现 Runnable 接口为主,因为相比之下 Runnable 有如下优势:

1 代码和数据分离,可被多个线程共享

例:实现打印当前线程名称的功能

(1) Thread 实现

class CustomThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

public static void main(String[] args) {
    Thread t1 = new CustomThread();
    Thread t2 = new CustomThread();
    Thread t3 = new CustomThread();
    t1.start();
    t2.start();
    t3.start();
}

测试代码新建了3个 CustomThread 类对象,实际上这三个对象的功能都是打印当前线程名称,测试结果:

这里写图片描述

(2) Runnable 实现

class CustomRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

public static void main(String[] args) {
    Runnable runnable = new CustomRunnable();
    Thread t1 = new Thread(runnable);
    Thread t2 = new Thread(runnable);
    Thread t3 = new Thread(runnable);
    t1.start();
    t2.start();
    t3.start();
}

测试代码新建了3个 java.lang.Thread 类对象,共享同一个 CustomRunnable 对象,CustomRunnable 对象功能就是打印当前线程名称,测试结果:

这里写图片描述

2 适用于多个相同逻辑的线程处理同一资源

通过经典的售票系统案例展示继承 java.lang.Thread 类和实现 java.lang.Runnable 接口的差异

(1) Thread 实现

class TicketThread extends Thread {
    private int tickets = 5;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + " Left tickets number : " + (--tickets));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + " All tickets have been saled!");
                break;
            }
        }
    }
}

public static void main(String[] args) {
    new TicketThread().start();
    new TicketThread().start();
    new TicketThread().start();
}

测试结果:

这里写图片描述

通过测试结果可以看出,Thread 实现相当于多个线程分别完成各自的任务。以售票为例,根据售票窗口数量(线程数量)均分总票数,各窗口负责销售分配给自己的票数,这样可能会出现某些窗口提前销售完所有自己负责的票数,而有些窗口却还有票未售完。

(2) Runnable 实现

class TicketRunnable implements Runnable {
    private int tickets = 5;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + " Left tickets number : " + (--tickets));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + " All tickets have been saled!");
                break;
            }
        }
    }
}

public static void main(String[] args) {
    Runnable ticket = new TicketRunnable();
    new Thread(ticket).start();
    new Thread(ticket).start();
    new Thread(ticket).start();
}

测试结果:

这里写图片描述

通过测试结果可以看出,所有线程共同完成一个任务。以售票为例,所有窗口都会一直售票到最后一张票售出。

需要额外注意,从打印结果可以看出,ticket 的售出顺序并非倒序,这是因为线程执行时机难以预测,而 –tickets 并非原子操作

多个 Thread 对象共同执行同一个 Runnable 对象中的代码是非线程安全的,如果代码中没有加线程休眠的代码,很可能后续会打印出 Left tickets number : -1,因为一个线程在判断剩余票数还剩 1 张时,还没有来得及减 1,便切换到另一个线程将票数减 1,变为了 0,那么接下来之前的线程再将票数减 1,便得到了 -1。

3 避免 Java 单继承特性的局限,面向接口的编程更为合理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

又言又语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值