Java Thread 和 Runnable 详解

在 Android 开发中,多线程编程是处理耗时操作(如网络请求、数据库读写、复杂计算)的基础。ThreadRunnable 是 Java 中最原始、最核心的线程机制,Android 完全继承了这套体系。本文将详细讲解它们的概念、用法、区别以及在 Android 中的注意事项,不涉及协程相关内容。


1. 基本概念

1.1 Thread(线程)

  • Thread 是 Java 中代表一个独立执行路径的类。

  • 每个 Thread 对象对应操作系统中的一个实际线程(或映射到内核线程,具体取决于 JVM 实现)。

  • 线程拥有自己的程序计数器、栈和局部变量,但共享进程内的堆内存和静态变量。

  • 在 Android 中,主线程(UI 线程)也是一个 Thread 实例,负责处理界面绘制和用户交互。

1.2 Runnable(任务)

  • Runnable 是一个函数式接口,只声明了一个方法 void run()

  • 它本身不是线程,只是一个可以被线程执行的任务描述。

  • 实现了 Runnable 的类可以被提交给 Thread 对象执行,也可以被线程池(如 ExecutorService)执行。

  • Android 中的 Handler.post(Runnable) 等也利用了 Runnable 作为任务载体。


2. 创建线程的两种方式

2.1 继承 Thread 类

复制代码
class MyThread extends Thread {
    @Override
    public void run() {
        // 在线程中执行的代码
        System.out.println("Thread running: " + Thread.currentThread().getName());
    }
}

// 使用
MyThread t = new MyThread();
t.start(); // 启动新线程

2.2 实现 Runnable 接口

复制代码
class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 任务代码
        System.out.println("Runnable running: " + Thread.currentThread().getName());
    }
}

// 使用
Thread t = new Thread(new MyRunnable());
t.start();

2.3 使用匿名内部类或 Lambda(Java 8+)

复制代码
// 匿名内部类
Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        // 代码
    }
});
t1.start();

// Lambda 表达式(Runnable 是函数式接口)
Thread t2 = new Thread(() -> {
    // 代码
});
t2.start();

3. start() 与 run() 的区别

这是新手最容易混淆的地方:

方法 作用 是否创建新线程
start() 启动线程,JVM 会调用该线程的 run() 方法,并在新线程中执行
run() 仅仅是普通方法调用,在当前线程中执行 run() 方法体
复制代码
Thread t = new Thread(() -> {
    System.out.println("线程名: " + Thread.currentThread().getName());
});

t.run();   // 输出:线程名: main (因为仍在主线程执行)
t.start(); // 输出:线程名: Thread-0 (新建的线程)

重要: 要真正开启一个新线程,必须调用 start(),而不是 run()


4. Thread 的常用方法和生命周期

4.1 常用方法

  • start():启动线程

  • run():线程体

  • sleep(long millis):静态方法,让当前线程休眠指定毫秒,不会释放锁

  • join():等待该线程终止

  • interrupt():中断线程(设置中断标志,并不会立即停止线程)

  • isAlive():判断线程是否存活

  • setPriority(int):设置线程优先级(1~10,默认5)

  • setDaemon(boolean):设置是否为守护线程(当所有非守护线程结束时,JVM 退出,不论守护线程是否执行完毕)

  • setName(String) / getName():设置/获取线程名

4.2 生命周期状态

线程在 Thread.State 枚举中定义了六种状态:

  1. NEW :创建但尚未调用 start()

  2. RUNNABLE:可运行状态,包括正在运行和等待 CPU 调度

  3. BLOCKED:阻塞,等待获取锁

  4. WAITING :无限等待,通过 wait()join() 等进入

  5. TIMED_WAITING :限时等待,通过 sleep()wait(timeout) 等进入

  6. TERMINATED:线程执行完毕

状态转换图略,理解即可。


5. Thread 与 Runnable 的关系与区别

5.1 关系

  • Thread 是线程的抽象,负责创建和控制线程。

  • Runnable 是任务的抽象,负责定义要执行的具体逻辑。

  • Thread 内部可以持有一个 Runnable 对象作为任务(通过构造方法传入)。

  • Thread 类本身实现了 Runnable 接口,所以也可以把 Thread 当作一个任务交给另一个 Thread 执行。

5.2 区别

维度 继承 Thread 实现 Runnable
类继承限制 Java 单继承,若继承 Thread 则无法再继承其他类 可以实现多个接口,不占用继承名额
资源共享 每个线程是独立的 Thread 子类实例,资源不易共享(除非用 static) 同一个 Runnable 实例可以被多个 Thread 共享,更适合资源共享场景
代码组织 线程和任务绑定在一起,耦合度高 任务与线程控制分离,更符合单一职责原则
灵活性 较差,一旦继承 Thread 就不能更改任务逻辑 更灵活,同一个 Runnable 可以提交给不同线程或线程池
适用场景 适合简单的、独立的线程,不需要共享太多数据 适合需要将任务与线程分离、多个线程执行相同任务的情况

结论: 一般推荐实现 Runnable 接口,因为可以避免单继承限制,并且任务与线程控制分离,代码结构更清晰。


6. 在 Android 中使用 Thread 的注意事项

6.1 主线程与子线程

  • Android 的主线程(UI 线程)负责处理界面更新、用户交互等,不能执行耗时操作,否则会导致 ANR(应用无响应)。

  • 耗时操作(网络、数据库、大量计算)必须在子线程中执行。

  • 使用 ThreadRunnable 可以轻松创建子线程。

复制代码
new Thread(() -> {
    // 耗时操作,比如下载文件
    String data = downloadData();
    
    // 不能直接更新 UI,会抛出 CalledFromWrongThreadException
    // textView.setText(data); // 错误!
}).start();

6.2 更新 UI 的方式

子线程中不能直接更新 UI,必须通过以下方式:

  • Activity.runOnUiThread(Runnable)

  • View.post(Runnable)

  • Handler(主线程 Handler 发送消息或 Runnable)

复制代码
new Thread(() -> {
    String data = downloadData();
    
    // 方式一:runOnUiThread
    runOnUiThread(() -> textView.setText(data));
    
    // 方式二:View.post
    textView.post(() -> textView.setText(data));
    
    // 方式三:Handler
    mainHandler.post(() -> textView.setText(data));
}).start();

6.3 线程泄漏

  • 在 Activity 或 Fragment 中启动线程,如果线程持有 Activity 的引用,并且线程长时间运行,可能导致 Activity 无法被回收,造成内存泄漏。

  • 解决方法:在 onDestroy() 中停止线程,或者使用静态内部类 + 弱引用,更推荐使用线程池或 AsyncTask(已废弃)或协程(此处不讨论)。

6.4 线程数量控制

  • 频繁创建和销毁线程开销较大,且大量线程会消耗系统资源。

  • 在 Android 中,对于多任务场景,应优先使用线程池(ExecutorService),而不是无限创建 Thread 对象。


7. 示例:使用 Thread 和 Runnable 实现一个简单的异步任务

以下代码演示如何在 Android 中结合 ThreadRunnableHandler 完成网络请求并更新 UI。

复制代码
public class MainActivity extends Activity {
    private TextView textView;
    private Handler mainHandler = new Handler(Looper.getMainLooper());

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text_view);

        // 创建 Runnable 任务
        Runnable fetchDataTask = new Runnable() {
            @Override
            public void run() {
                // 模拟耗时操作
                String result = fetchDataFromNetwork();
                
                // 切回主线程更新 UI
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText(result);
                    }
                });
            }
        };

        // 创建并启动线程
        Thread workerThread = new Thread(fetchDataTask);
        workerThread.start();
    }

    private String fetchDataFromNetwork() {
        try {
            Thread.sleep(2000); // 模拟网络延迟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "数据加载完成";
    }
}

8. 总结

  • Thread 是线程,Runnable 是任务。推荐使用实现 Runnable 的方式将任务与线程分离。

  • 调用 start() 才会启动新线程,直接调用 run() 只是在当前线程执行方法体。

  • 在 Android 中,所有耗时操作必须在子线程,但 UI 更新必须在主线程。

  • 注意线程的生命周期和资源管理,避免内存泄漏和过度创建线程。

  • 掌握 ThreadRunnable 是理解 Android 多线程和后续学习 Handler、线程池、协程的基础。


Runnable 本身不能独立运行,它只是一个包含 run() 方法的接口,用来描述一段需要执行的代码。它必须被某个执行环境调用其 run() 方法才能真正执行,而这个执行环境本质上是一个线程(或线程池中的某个线程)。

最常见的执行方式有:

  1. 直接使用 Thread

    复制代码
    new Thread(runnable).start();

    这是最基础的方式,Thread 会创建一个新线程,并在该线程中调用 Runnablerun() 方法。

  2. 使用 ExecutorService(线程池)

    复制代码
    ExecutorService executor = Executors.newFixedThreadPool(3);
    executor.execute(runnable);  // 线程池中的某个线程会执行它

    线程池内部也是由多个 Thread 组成的,所以本质上还是由线程执行。

  3. 通过 Handler 投递到消息队列

    复制代码
    Handler handler = new Handler(Looper.getMainLooper());
    handler.post(runnable);  // 主线程会在处理消息时执行它

    这里 Runnable 会被主线程(也是一个 Thread)在某个时刻调用 run(),但它并不是直接新建一个 Thread 去执行。

所以更准确的说法是:

Runnable 不能独立运行,它必须被某个线程(或线程池、Handler 等执行机制)调用其 run() 方法才能执行。

虽然不一定直接通过 new Thread(runnable),但执行它的最终载体始终是某个线程。

相关推荐
mmsx1 小时前
osmdroid Overlay 体系详解:比例尺/罗盘/总绘图层有序叠放
android·源码·地图·osmdroid
又见情义3 小时前
RK3568 Android 13 开机时间优化-HAL 服务延时启动
android
v_34836087623 小时前
Android8 开机动画结束分析
android
美狐美颜SDK开放平台5 小时前
直播APP开发核心功能详解:美颜SDK、音视频、礼物等功能成本怎么计算?
android·人工智能·计算机视觉·音视频·直播美颜sdk
安河桥。5 小时前
Android视频流处理模块硬件加速器数据流转技术说明
android·图像处理·isp
hunterandroid5 小时前
[Android 从零到一] APK 体积优化实战:从 50MB 到 15MB 的瘦身之旅
android·前端
迪飞特科技5 小时前
《MyBatis 批量插入性能优化的 5 个关键参数配置》
android·性能优化·mybatis
mmsx6 小时前
osmdroid 手势交互与罗盘方向:缩放联动+传感器融合+定位跟随
android·源码·地图·osmdroid