当前位置: 首页 > news >正文

Java多线程编程指南

Java 多线程编程详解

什么是多线程

多线程是 Java 中实现并发编程的核心机制,允许程序同时执行多个任务。在 Java 中,主线程(main 方法)是程序的入口点,我们可以通过创建额外的线程来实现多任务并行处理。

线程的创建方式

Java 提供了三种主要的线程创建方式,每种方式都有其适用场景。

1. 继承 Thread 类

继承 Thread 类并重写其 run() 方法是创建线程的基本方式。

public class ThreadExtendExample {public static void main(String[] args) {// 创建线程对象MyThread myThread = new MyThread();// 启动线程(注意:调用start()而非直接调用run())myThread.start();// 主线程执行的代码for (int i = 0; i < 5; i++) {System.out.println("主线程执行: " + i);try {// 让主线程休眠500毫秒,便于观察线程交替执行Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}// 自定义线程类,继承Threadstatic class MyThread extends Thread {@Overridepublic void run() {// 线程要执行的任务for (int i = 0; i < 5; i++) {// 获取当前线程名称并输出System.out.println(Thread.currentThread().getName() + " 执行: " + i);try {// 线程休眠500毫秒Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}}
}

代码说明

  • 继承 Thread 类后必须重写 run() 方法,该方法包含线程要执行的任务
  • 启动线程必须调用 start() 方法,而非直接调用 run() 方法
  • start() 方法会创建一个新线程并让它执行 run() 方法中的代码
  • 可以通过 Thread.sleep() 方法让线程暂时休眠

2. 实现 Runnable 接口

实现 Runnable 接口是一种更灵活的方式,它避免了单继承的限制,推荐优先使用。

public class RunnableExample {public static void main(String[] args) {// 创建Runnable实现类对象MyRunnable myRunnable = new MyRunnable();// 创建线程对象,并将Runnable对象作为参数传递Thread thread1 = new Thread(myRunnable, "线程A");Thread thread2 = new Thread(myRunnable, "线程B");// 启动线程thread1.start();thread2.start();}// 实现Runnable接口static class MyRunnable implements Runnable {private int count = 0;@Overridepublic void run() {for (int i = 0; i < 5; i++) {count++;System.out.println(Thread.currentThread().getName() + " 计数: " + count + " 当前线程ID: " + Thread.currentThread().getId());try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}}}}
}

代码说明

  • Runnable 接口只有一个 run() 方法需要实现
  • 可以将同一个 Runnable 实例传递给多个 Thread 对象,实现资源共享
  • 可以通过 Thread 构造函数为线程指定名称,便于调试
  • 相比继承 Thread 类,这种方式更适合多个线程共享同一资源的场景

也可以使用匿名内部类简化 Runnable 的使用:

public class AnonymousRunnableExample {public static void main(String[] args) {// 使用匿名内部类创建线程Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("匿名线程执行中...");}}, "匿名线程");thread.start();}
}

Java 8 及以上版本还可以使用 Lambda 表达式进一步简化:

public class LambdaThreadExample {public static void main(String[] args) {// 使用Lambda表达式创建线程Thread thread = new Thread(() -> {System.out.println("Lambda线程执行中...");}, "Lambda线程");thread.start();}
}

3. 实现 Callable 接口

Callable 接口是 Java 5 引入的,它类似于 Runnable,但可以返回结果并抛出受检异常。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class CallableExample {public static void main(String[] args) {// 创建Callable实现类对象MyCallable myCallable = new MyCallable();// 创建FutureTask对象,用于接收返回结果FutureTask<Integer> futureTask = new FutureTask<>(myCallable);// 创建线程对象并启动Thread thread = new Thread(futureTask, "计算线程");thread.start();// 主线程可以做其他事情System.out.println("主线程执行其他任务...");try {// 获取线程执行结果,get()方法会阻塞直到结果返回Integer result = futureTask.get();System.out.println("线程计算结果: " + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}// 实现Callable接口,指定返回值类型为Integerstatic class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {System.out.println(Thread.currentThread().getName() + " 开始计算...");// 模拟耗时计算int sum = 0;for (int i = 1; i <= 100; i++) {sum += i;Thread.sleep(10); // 模拟计算耗时}System.out.println(Thread.currentThread().getName() + " 计算完成!");return sum; // 返回计算结果}}
}

代码说明

  • Callable 是一个泛型接口,需要指定返回值类型
  • call() 方法可以返回结果,而 run() 方法没有返回值
  • call() 方法可以抛出受检异常
  • 需要使用 FutureTask 来包装 Callable 对象,并获取返回结果
  • FutureTask.get() 方法会阻塞当前线程,直到获取到计算结果

多线程执行原理

  1. 当调用线程的 start() 方法时,JVM 会创建一个新的线程,并在该线程中执行 run() 方法
  2. 主线程和新创建的线程是并行执行的,它们的执行顺序由操作系统的线程调度机制决定
  3. 线程调度机制采用时间分片策略,快速切换不同线程执行,造成多线程同时执行的假象
  4. 即使主线程执行完毕,其他线程也会继续执行直到完成
  5. 所有非守护线程执行完毕后,JVM 才会退出
public class ThreadExecutionDemo {public static void main(String[] args) {System.out.println("主线程开始执行");// 创建并启动第一个线程Thread thread1 = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("线程1执行: " + i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}, "线程1");// 创建并启动第二个线程Thread thread2 = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("线程2执行: " + i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}, "线程2");thread1.start();thread2.start();// 主线程执行自己的任务for (int i = 0; i < 5; i++) {System.out.println("主线程执行: " + i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("主线程执行完毕");}
}

执行结果分析
上述代码的执行结果中,三个线程(主线程、线程1、线程2)的输出会交替出现,每次运行的顺序可能都不同,这正是多线程并发执行的特性。

Thread 类常用方法

方法说明
start()启动线程,JVM会调用该线程的run()方法
run()线程执行的任务代码,通常需要重写
getName()获取线程名称
setName(String name)设置线程名称
currentThread()返回当前正在执行的线程对象
yield()线程让步,暂停当前线程,让其他线程执行
join()线程插队,等待该线程执行完毕后再继续执行其他线程
sleep(long millis)让当前线程休眠指定毫秒数
interrupt()中断线程,设置线程的中断状态
isInterrupted()判断线程是否被中断
stop()强制停止线程(已过时,不推荐使用)
setPriority(int newPriority)设置线程优先级(1-10,默认5)
getPriority()获取线程优先级
setDaemon(boolean on)设置为守护线程(后台线程)
isDaemon()判断是否为守护线程
public class ThreadMethodsDemo {public static void main(String[] args) {Thread thread = new Thread(() -> {// 获取当前线程并设置名称Thread currentThread = Thread.currentThread();currentThread.setName("演示线程");System.out.println(currentThread.getName() + " 开始执行");System.out.println(currentThread.getName() + " 优先级: " + currentThread.getPriority());try {// 线程休眠1秒Thread.sleep(1000);for (int i = 0; i < 3; i++) {System.out.println(currentThread.getName() + " 执行中: " + i);// 每执行一次就让步一次if (i == 1) {System.out.println(currentThread.getName() + " 进行让步");Thread.yield();}}} catch (InterruptedException e) {System.out.println(currentThread.getName() + " 被中断");return;}System.out.println(currentThread.getName() + " 执行完毕");});// 设置线程优先级thread.setPriority(Thread.NORM_PRIORITY + 1);// 启动线程thread.start();// 主线程等待演示线程执行完毕try {System.out.println("主线程等待演示线程执行完毕...");thread.join(); // 等待线程执行完毕} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程执行完毕");}
}

线程安全

当多个线程同时操作同一个共享资源时,可能会出现数据不一致的问题,这就是线程安全问题。

public class ThreadSafetyProblem {public static void main(String[] args) {// 创建共享资源Counter counter = new Counter();// 创建多个线程操作共享资源Thread thread1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}}, "线程1");Thread thread2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}}, "线程2");// 启动线程thread1.start();thread2.start();// 等待两个线程执行完毕try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}// 预期结果应该是20000,但实际可能小于这个值System.out.println("最终计数: " + counter.getCount());}// 共享资源类static class Counter {private int count = 0;// 自增方法(线程不安全)public void increment() {count++; // 这行代码实际包含三个操作:读取、修改、写入}public int getCount() {return count;}}
}

问题分析
上述代码中,count++ 操作看似简单,实则包含三个步骤:

  1. 读取当前 count 值
  2. 将 count 值加 1
  3. 将结果写回 count

当两个线程同时执行这三个步骤时,可能会出现数据覆盖,导致最终结果小于预期的 20000。

线程同步(解决线程安全问题)

解决线程安全问题的核心是保证对共享资源的原子操作,即同一时间只能有一个线程访问共享资源。

1. 同步代码块

使用 synchronized 关键字创建同步代码块,将访问共享资源的核心代码包裹起来。

public class SynchronizedBlockExample {public static void main(String[] args) {// 创建共享资源SafeCounter counter = new SafeCounter();// 创建多个线程操作共享资源Thread thread1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}}, "线程1");Thread thread2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}}, "线程2");// 启动线程thread1.start();thread2.start();// 等待两个线程执行完毕try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}// 现在结果应该正确是20000System.out.println("最终计数: " + counter.getCount());}// 线程安全的计数器类static class SafeCounter {private int count = 0;// 锁对象,多个线程必须使用同一个锁对象private final Object lock = new Object();// 使用同步代码块保证线程安全public void increment() {synchronized (lock) { // 加锁count++;} // 自动解锁}public int getCount() {synchronized (lock) { // 读取也需要同步return count;}}}
}

代码说明

  • 同步代码块格式:synchronized(锁对象) { ... }
  • 锁对象可以是任意对象,但多个线程必须使用同一个锁对象
  • 进入同步代码块前需要获取锁,执行完毕后自动释放锁
  • 同一时间只有一个线程能获取到锁,保证了代码块的原子性

2. 同步方法

synchronized 关键字修饰在方法上,使整个方法成为同步方法。

public class SynchronizedMethodExample {public static void main(String[] args) {// 创建共享资源MethodCounter counter = new MethodCounter();// 创建多个线程操作共享资源Thread thread1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}}, "线程1");Thread thread2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}}, "线程2");// 启动线程thread1.start();thread2.start();// 等待两个线程执行完毕try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("最终计数: " + counter.getCount());}// 使用同步方法的计数器类static class MethodCounter {private int count = 0;// 同步实例方法,隐式锁是this对象public synchronized void increment() {count++;}// 同步实例方法public synchronized int getCount() {return count;}// 静态同步方法,隐式锁是类对象(MethodCounter.class)public static synchronized void staticMethod() {// 静态方法中的同步代码}}
}

代码说明

  • 同步方法有隐式的锁对象:
    • 实例方法的锁对象是 this(当前对象)
    • 静态方法的锁对象是类的 Class 对象(如 MethodCounter.class
  • 同步方法的整个方法体都是同步的,保证了方法的原子性

3. Lock 锁

Lock 是 Java 5 引入的更灵活的锁机制,比 synchronized 功能更强大。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockExample {public static void main(String[] args) {// 创建共享资源LockCounter counter = new LockCounter();// 创建多个线程操作共享资源Thread thread1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}}, "线程1");Thread thread2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.increment();}}, "线程2");// 启动线程thread1.start();thread2.start();// 等待两个线程执行完毕try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("最终计数: " + counter.getCount());}// 使用Lock锁的计数器类static class LockCounter {private int count = 0;// 创建Lock锁对象,使用ReentrantLock实现private final Lock lock = new ReentrantLock();public void increment() {lock.lock(); // 加锁try {// 访问共享资源的核心代码count++;} finally {lock.unlock(); // 解锁,放在finally中确保一定会执行}}public int getCount() {lock.lock(); // 加锁try {return count;} finally {lock.unlock(); // 解锁}}}
}

代码说明

  • Lock 是接口,常用实现类是 ReentrantLock
  • 使用步骤:
    1. 创建 Lock 对象:Lock lock = new ReentrantLock();
    2. 在操作共享资源前调用 lock.lock() 加锁
    3. finally 块中调用 lock.unlock() 解锁,确保锁一定会释放
  • Lock 相比 synchronized 提供了更多功能,如尝试获取锁、可中断锁、超时锁等

线程池

线程池是一种管理线程的机制,它可以重复利用线程,避免频繁创建和销毁线程带来的性能开销。

线程池的优势

  1. 减少线程创建和销毁的开销
  2. 控制并发线程数量,避免资源耗尽
  3. 提高响应速度,线程可以立即处理任务
  4. 便于管理和监控线程

线程池的使用

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class ThreadPoolExample {public static void main(String[] args) {// 1. 创建线程池,指定核心线程数为3ExecutorService executorService = Executors.newFixedThreadPool(3);// 2. 提交任务for (int i = 0; i < 10; i++) {final int taskNum = i;// 提交Runnable任务executorService.execute(() -> {System.out.println("任务 " + taskNum + " 由线程 " + Thread.currentThread().getName() + " 执行");try {// 模拟任务执行时间TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}});}// 3. 关闭线程池executorService.shutdown();try {// 等待所有任务完成,最多等待10秒if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {// 如果超时,强制关闭executorService.shutdownNow();}} catch (InterruptedException e) {executorService.shutdownNow();}System.out.println("所有任务执行完毕");}
}

常见的线程池类型

  1. newFixedThreadPool(int nThreads):创建固定大小的线程池

    • 核心线程数和最大线程数都是指定的 nThreads
    • 适用于任务数量已知且相对稳定的场景
  2. newCachedThreadPool():创建可缓存的线程池

    • 核心线程数为0,最大线程数为 Integer.MAX_VALUE
    • 线程空闲60秒后会被回收
    • 适用于短期任务较多的场景
  3. newSingleThreadExecutor():创建单线程的线程池

    • 只有一个线程执行任务,任务按顺序执行
    • 适用于需要保证任务顺序执行的场景
  4. newScheduledThreadPool(int corePoolSize):创建可定时执行任务的线程池

    • 可以延迟执行或定期执行任务
    • 适用于需要定时任务的场景

线程池的核心参数(ThreadPoolExecutor)

对于更复杂的场景,可以直接使用 ThreadPoolExecutor 类创建线程池,它有更多可配置的参数:

ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize,        // 核心线程数maximumPoolSize,     // 最大线程数keepAliveTime,       // 非核心线程空闲时间unit,                // 时间单位workQueue,           // 任务队列threadFactory,       // 线程工厂handler              // 拒绝策略
);

任务拒绝策略

当线程池中的线程数达到最大线程数且任务队列已满时,新提交的任务会被拒绝,此时会触发拒绝策略:

  1. AbortPolicy:直接抛出 RejectedExecutionException 异常(默认策略)
  2. CallerRunsPolicy:由提交任务的线程自己执行该任务
  3. DiscardPolicy:直接丢弃该任务,不抛出异常
  4. DiscardOldestPolicy:丢弃任务队列中最旧的任务,然后尝试提交新任务

并行与并发

  • 并行(Parallelism):多个任务同时执行,需要多个CPU核心支持

    • 例如:两个CPU核心同时处理两个不同的任务
  • 并发(Concurrency):多个任务交替执行,通过CPU时间分片实现

    • 例如:一个CPU核心快速切换处理多个任务,造成同时执行的假象

在Java多线程编程中,我们通常关注的是并发编程,通过多线程实现并发处理,提高程序的执行效率和响应速度。

总结

Java多线程编程是实现高效并发程序的基础,本文介绍了线程的创建方式、执行原理、常用方法、线程安全问题及解决方案,以及线程池的使用。掌握多线程编程可以帮助我们开发出更高效、响应更快的应用程序,但同时也需要注意线程安全问题,合理设计同步机制,避免死锁、活锁等问题的发生。

在实际开发中,应根据具体场景选择合适的线程创建方式和同步机制,并合理使用线程池管理线程资源,以提高程序性能和可靠性。

http://www.dtcms.com/a/393353.html

相关文章:

  • 【论文速读】基于地面激光扫描(TLS)和迭 代最近点(ICP)算法的土坝监测变形分析
  • GAMES101:现代计算机图形学入门(Chapter2 向量与线性代数)迅猛式学线性代数学习笔记
  • 汉语构词智慧:从历史优势到现实考量——兼论“汉语全面改造英语”的可能性
  • 仿tcmalloc高并发内存池
  • 墨者学院-通关攻略(持续更新持续改进)
  • 10厘米钢板矫平机:把“波浪”压成“镜面”的科学
  • ESP32- 项目应用1 智能手表之网络配置 #6
  • TCP/IP 互联网的真相:空间域和时间域的统计学
  • 同步与异步
  • C++中char与string的终极对比指南
  • Java基础 9.20
  • U228721 反转单链表
  • 串行总线、并行总线
  • `HTML`实体插入软连字符: `shy;`
  • 日志驱动切换针对海外vps日志收集的操作标准
  • Zynq开发实践(SDK之自定义IP2 - FPGA验证)
  • 广东电信RTSP单播源参数解析
  • 关于工作中AI Coding的一些踩坑经验
  • MyBatis 参数传递详解:从基础到复杂场景全解析
  • ego(8)---L-BFGS优化算法与B样条生成最终轨迹
  • 【开题答辩全过程】以 HPV疫苗预约网站为例,包含答辩的问题和答案
  • Linux网络中Socket网络套接字的高级应用与优化策略
  • 人才测评系统选型参考:含国内平台对比
  • 人才素质测评在线测评系统平台清单:5款推荐
  • 【语法进阶】匹配分组
  • 猫头虎AI开源项目分享:通过压缩-感知-扩展来改善RAG应用延迟的高效框架:REFRAG,速度快、质量高
  • 某音a_bogus纯算法192位研究分析
  • RAG vs 长文本模型:技术原理、适用场景与选型指南
  • PowerBI自定义函数
  • FreeRTOS——信号量,互斥锁,临界区,延时