首页 > 趣闻 > 正文内容

Java中两种线程创建方式对比:Thread类与Runnable接口区别

趣闻2025-05-27 15:57:02

有没有遇到过这样的场景?你写的程序明明逻辑没问题,但运行起来就像老牛拉破车一样慢,特别是处理多个任务时直接卡成"PPT模式"?今天咱们就掰开了揉碎了聊聊Java里解决这个问题的两大杀器——Thread类和Runnable接口。(悄悄说句实话,我当年学这块的时候,差点被这两个家伙绕晕!)


为什么Java要搞两种创建线程的方式?

这个问题就像问"为什么手机要有安卓和iOS",本质上都是为了应对不同场景。咱们先看段经典代码:

java复制
// Thread类写法
class MyThread extends Thread {
    public void run() {
        System.out.println("我是继承Thread的线程");
    }
}

// Runnable接口写法
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("我是实现Runnable的线程");
    }
}

乍看是不是像双胞胎?但仔细看就会发现门道:??继承Thread类??需要extends,而??实现Runnable接口??用的是implements。这里埋着Java设计者的大智慧——单继承限制。

举个栗子:假设你爸是王健林(继承Thread),就不能同时认马云当爹(继承其他类)。但如果是当打工人(实现Runnable),你完全可以同时掌握编程、烹饪、开挖掘机多种技能!


两种方式到底怎么用?

先看Thread类的典型用法:

java复制
public class Main {
    public static void main(String[] args) {
        new MyThread().start(); // 注意这里要调start()方法
    }
}

这里有个新手必踩的坑:很多小白直接调用run()方法,结果发现根本没启动新线程!这就好比买了辆跑车却用人力推着走——完全没发挥发动机的作用。

再看Runnable的正确打开方式:

java复制
public class Main {
    public static void main(String[] args) {
        new Thread(new MyRunnable()).start();
    }
}

有没有发现关键点?Runnable的实例需要包裹在Thread对象里。这种设计就像去奶茶店点单:Runnable是奶茶配方,Thread才是真正动手制作的店员。


核心区别全解析

咱们直接上硬核对比表格:

??对比维度????Thread类????Runnable接口??
继承机制占用唯一继承名额不占用继承名额
资源共享每个线程都是独立实例多个线程可共享同一个实例
代码耦合度高(线程逻辑与线程对象绑定)低(线程逻辑与执行机制解耦)
内存消耗每个线程都需新建对象(较耗内存)可复用同一对象(节省内存)
适用场景简单独立任务复杂任务/需要资源池化的场景

看到这里可能有同学要问:那到底该选哪个?根据我五年开发经验,??除非必须重写Thread的方法,否则无脑选Runnable就对了??。特别是在Web开发中,线程池里塞的都是Runnable任务,这个设计可不是拍脑袋决定的。


实战中的血泪教训

去年在电商项目里遇到个典型case:促销活动时需要同时处理10万条订单,新手程序员小明用了Thread类创建线程,结果直接OOM(内存溢出)了。后来改用Runnable+线程池,内存消耗直接降了80%!

这里面的门道在于:用Thread类的话,每个订单处理都要new一个Thread对象;而用Runnable,只需要new一个任务对象丢给线程池,就像把快递包裹交给快递站,不用自己雇快递员。


那些年我们踩过的坑

  1. ??start()和run()傻傻分不清??:

    • start()会启动新线程执行run()方法
    • 直接调用run()就变成了普通方法调用
  2. ??线程安全问题??:

    java复制
    // 错误示范
    class Counter extends Thread {
        static int count = 0;
        public void run() {
            for(int i=0; i<1000; i++) count++;
        }
    }
    // 启动10个线程后count远小于10000

    这个问题在Runnable实现中同样存在,但Runnable更便于加锁处理,毕竟可以用同一个锁对象。

  3. ??匿名内部类陷阱??:

    java复制
    new Thread(new Runnable() {
        public void run() {
            System.out.println(this.getClass().getName());
        }
    }).start();

    这里的this指向的是Runnable匿名类,而不是外围类。这个坑我亲自踩过,调试了俩小时才发现!


个人开发心得

干了这么多年Java,发现一个有趣现象:老项目里遍地都是Thread的子类,而新项目基本清一色的Runnable+Lambda表达式。这不是说Thread类要被淘汰了,而是Runnable的设计确实更符合现代编程的"组合优于继承"原则。

最近在搞的一个物联网项目里,所有设备通信模块都用的Runnable。最香的是能方便地配合线程池管理,还能用CompletableFuture玩异步编程。要是用Thread类的话,光线程管理代码就得写吐了。

不过话又说回来,Thread类也不是一无是处。上周帮实习生调试代码,发现他在Android里用HandlerThread(Thread的子类)处理后台任务,这种特定场景下的选择就非常明智。所以啊,编程没有银弹,合适的就是最好的。

最后给个忠告:新手阶段先把两种方式都吃透,等真正理解了Java线程模型的设计哲学,自然就知道什么时候该用什么了。就像学做菜,先把刀工练扎实了,后面颠勺摆盘都是水到渠成的事!

搜索