Java中编写线程主要有两种方式:继承Thread
类和实现Runnable
接口,以下是详细的实现方法、原理及注意事项:
通过继承Thread类创建线程
这是最基础的方式,但因违反面向对象设计的单一职责原则(既承担业务逻辑又具备线程属性),实际开发中使用较少,步骤如下:
- 定义子类:新建一个类继承自
java.lang.Thread
; - 重写run()方法:将需要在新线程执行的代码写入此方法;
- 启动线程:调用
start()
触发JVM调用run()
(注意不要直接调用run()
,否则不会启动新线程)。class MyThread extends Thread { @Override public void run() { System.out.println("线程执行中..."); } } // 使用示例 public class Main { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); // 正确启动方式 } }
⚠️ 缺点:无法与其他类共享同一资源(如多个线程需要处理相同类型的任务时),且破坏了类的层次结构。
通过实现Runnable接口(推荐方案)
这种方式更灵活且符合解耦思想,适合多任务复用同一个实例的场景,核心步骤为:
- 实现Runnable接口:任意类均可声明实现该接口,并实现其唯一的抽象方法
run()
; - 封装到Thread对象:以该类的实例作为参数构造新的
Thread
对象; - 启动线程:同样通过
start()
方法激活。class Task implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "计数:" + i); } } } // 使用示例 public class Main { public static void main(String[] args) { Task task = new Task(); Thread thread1 = new Thread(task, "子线程A"); Thread thread2 = new Thread(task, "子线程B"); thread1.start(); thread2.start(); } }
✅ 优势:①避免单继承限制;②便于多个线程共享同一个
Runnable
目标对象;③支持线程池管理。
关键机制解析
特性 | 说明 | 示例场景 |
---|---|---|
调度模型 | Java依赖底层OS进行抢占式调度,每个线程由CPU分配时间片轮流执行 | 高并发IO操作不阻塞主程序 |
内存可见性问题 | 各线程拥有独立的工作内存副本,修改后需通过volatile /锁保证主存同步 |
计数器累加时出现数据不一致 |
守护线程(Daemon) | 设置为后台线程(setDaemon(true) ),当所有用户线程结束时自动终止 |
日志记录、监控等辅助功能 |
优先级控制 | setPriority(int level) 范围1~10,实际效果取决于操作系统支持情况 |
实时性要求高的音频流处理 |
高级用法扩展
Lambda表达式简化写法(JDK8+)
利用函数式编程特性可进一步缩减代码量:
new Thread(() -> { while (!flag) { / ... / } }).start();
或配合线程池工厂:
ExecutorService pool = Executors.newFixedThreadPool(4); pool.submit(() -> System.out.println("异步任务"));
Callable与Future获取返回值
相较于Runnable只能执行无结果的任务,Callable<V>
允许定义泛型返回值,并通过Future
获取异步计算结果:
Callable<Integer> callable = () -> { return 42; }; Future<Integer> future = executor.submit(callable); Integer result = future.get(); // 阻塞直到结果可用
常见陷阱与解决方案
问题现象 | 根本原因 | 解决策略 |
---|---|---|
数据竞争导致脏读 | 未正确同步共享变量 | 使用synchronized 块/锁机制 |
死锁(Deadlock) | 多把锁获取顺序不一致 | 固定全局加锁顺序 |
活锁(Livelock) | 条件判断错误导致无限循环让步 | 引入随机延迟重试机制 |
Starvation饥饿 | 低优先级线程长期得不到调度 | 合理设置线程优先级区间 |
相关问答FAQs
Q1: Java为什么不允许直接调用run()方法来启动线程?
A: 因为直接调用run()
相当于普通的方法调用,仍在当前线程串行执行;而start()
会通知JVM创建新栈帧和新程序计数器,真正实现并发执行,这是Java线程模型的核心设计机制。
Q2: 如何确保多个线程按顺序执行特定代码段?
A: 可通过以下任一方案实现:①使用synchronized
关键字修饰共享对象作为监视器;②采用ReentrantLock
显式锁并配合Condition
条件队列;③利用CountDownLatch
/CyclicBarrier
等同步工具类控制时序依赖关系,例如银行转账场景必须保证账户余额修改的原子
原创文章,发布者:酷盾叔,转转请注明出处:https://www.kd.cn/ask/133233.html