线程配置经验

news2025/1/31 2:51:51

工作时,时常会遇到,线程相关的问题与解法,本人会持续对开发过程中遇到的关于线程相关的问题及解决记录更新记录在此篇博客中。

目录

一、线程基本知识

1. 线程和进程

二、问题与解法

1. 避免乘法级别数量线程并行

1)使用线程池

 2)任务队列

3)限制并发任务数量

 4)任务分批处理

2. 避免函数被重复调用

1)使用标志位

2)使用同步锁

3)使用AOP和缓存

4)使用消息队列

5)使用限流算法


一、线程基本知识

1. 线程与进程

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。例如,任务管理器显示的就是进程:

线程是比进程更小的执行单位。一个进程在其执行过程中可以产生多个线程。与进程不同的是同类的多个线程共享堆和方法区资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈

JDK 1.2之前,Java线程是基于绿色线程实现的,这是一种用户级线程。即JVM自己模拟了多线程的运行,而不依赖于操作系统。由于绿色线程和原生线程比起来的在使用时有一些限制(比如,绿色线程不能直接使用操作系统提供的功能如异步 I/O、只能在一个内核线程上运行无法利用多核);在JDK 1.2之后,Java线程改为使用原生线程来实现。也就是说 JVM 直接使用操作系统原生的内核级线程(内核线程)来实现 Java 线程,由操作系统内核进行线程的调度和管理。

  • 用户线程:由用户空间程序管理和调度的线程,运行在用户空间(专门给应用程序使用)。
  • 内核线程:由操作系统内核管理和调度的线程,运行在内核空间(只有内核程序可以访问)。

顺便简单总结一下用户线程和内核线程的区别和特点:用户线程创建和切换成本低,但不可以利用多核。内核态线程,创建和切换成本高,可以利用多核。现在的 Java 线程的本质其实就是操作系统的线程

线程模型是用户线程和内核线程之间的关联方式,常见的线程模型有这三种:

  1. 一对一(一个用户线程对应一个内核线程)
  2. 多对一(多个用户线程映射到一个内核线程)
  3. 多对多(多个用户线程映射到多个内核线程)

常见的三种线程模型

在 Windows 和 Linux 等主流操作系统中,Java 线程采用的是一对一的线程模型,也就是一个 Java 线程对应一个系统内核线程。


一个进程中可以有多个线程,多个线程共享进程的方法区(JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。

  • 程序计数器为什么私有:程序计数器是为了线程切换后,能够回到正确的执行位置。
  • 虚拟机栈为什么私有:每个Java栈帧在执行之前会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至完成的过程,对应着一个栈帧在Java虚拟机栈中入栈和出栈的过程。
  • 本地方法栈为什么私有:和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象(几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

二、问题与解法

1. 避免乘法级别数量线程并行

当一个多线程函数被重复调用,就造成了并行线程数量成为乘法级别。

这种情况通常不是故意的,而是不小心。

一旦发生这种情况,需要及时排查,不然线程太多、资源混乱,服务器上其他服务请求被搁置,就变成了事故。

例如:一个大批量执行、耗时、数据量大的任务。现在使用三个线程、并行生成结果这个函数 每三分钟执行一次可能下一次调用开始时,旧的线程还没执行结束。

count (并行线程数)= m(调用次数) * n(单词调用线程数)

乘法级别的线程在处理这些数据,不仅不能提高处理效率、反而由于线程太多造成了资源混乱、大量资源浪费。导致正常的请求无法及时被相应,影响到服务器上其他服务的正常使用。甚至可能导致服务器宕机。

可以使用线程池、任务队列、限制并发任务数量、任务分批处理来解决。

1)使用线程池

线程池可以有效管理线程的生命周期,避免频繁创建和销毁线程带来的开销,并且可以限制并发线程的数量。Java提供了 ExecutorService 来实现线程池。

注意,线程池多线程调用也会出现乘法级别数量的线程

示例代码:

import java.util.concurrent.*;

public class TaskScheduler {
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    private final ExecutorService threadPool = Executors.newFixedThreadPool(3);

    public void scheduleTask(Runnable task) {
        scheduler.scheduleAtFixedRate(() -> {
            try {
                // 提交任务到线程池
                for (int i = 0; i < 3; i++) {
                    threadPool.submit(task);
                }
            } catch (RejectedExecutionException e) {
                // 处理线程池拒绝任务的情况
                System.out.println("Task rejected, possibly due to shutdown or overload.");
            }
        }, 0, 3, TimeUnit.MINUTES);
    }

    public void shutdown() {
        scheduler.shutdown();
        threadPool.shutdown();
    }

    public static void main(String[] args) {
        TaskScheduler scheduler = new TaskScheduler();
        Runnable task = () -> {
            // 你的任务逻辑
            System.out.println("Task executed by " + Thread.currentThread().getName());
        };
        scheduler.scheduleTask(task);

        // 程序结束时关闭线程池
        Runtime.getRuntime().addShutdownHook(new Thread(scheduler::shutdown));
    }
}

优点:

  • 限制线程数量:通过固定大小的线程池,可以避免线程过多导致的资源竞争。

  • 任务调度ScheduledExecutorService 可以定时执行任务,避免任务重叠。

  • 优雅关闭:通过 shutdown() 方法可以优雅地关闭线程池。

 2)任务队列

如果任务的执行时间不确定,可以使用任务队列来管理任务。线程池会从任务队列中获取任务并执行。

import java.util.concurrent.*;

public class TaskQueueExample {
    private final ExecutorService threadPool = Executors.newFixedThreadPool(3);
    private final BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();

    public void addTask(Runnable task) {
        try {
            taskQueue.put(task);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public void startProcessing() {
        for (int i = 0; i < 3; i++) {
            threadPool.submit(() -> {
                while (!Thread.currentThread().isInterrupted()) {
                    try {
                        Runnable task = taskQueue.take();
                        task.run();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            });
        }
    }

    public void shutdown() {
        threadPool.shutdown();
    }

    public static void main(String[] args) throws InterruptedException {
        TaskQueueExample example = new TaskQueueExample();
        example.startProcessing();

        // 模拟添加任务
        for (int i = 0; i < 10; i++) {
            example.addTask(() -> {
                System.out.println("Task executed by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 模拟耗时任务
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 程序结束时关闭线程池
        Runtime.getRuntime().addShutdownHook(new Thread(example::shutdown));
    }
}

优点:

  • 任务排队:任务会被放入队列,按顺序执行,避免任务重叠。

  • 线程复用:线程池中的线程会复用,减少线程创建和销毁的开销。

3)限制并发任务数量

如果任务的执行时间较长,可以限制同时执行的任务数量。例如,每次只允许 3 个任务并发执行。

示例代码:

import java.util.concurrent.*;

public class LimitedConcurrencyExample {
    private final Semaphore semaphore = new Semaphore(3);
    private final ExecutorService threadPool = Executors.newFixedThreadPool(3);

    public void addTask(Runnable task) {
        threadPool.submit(() -> {
            try {
                semaphore.acquire(); // 获取许可
                task.run();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                semaphore.release(); // 释放许可
            }
        });
    }

    public void shutdown() {
        threadPool.shutdown();
    }

    public static void main(String[] args) {
        LimitedConcurrencyExample example = new LimitedConcurrencyExample();
        for (int i = 0; i < 10; i++) {
            example.addTask(() -> {
                System.out.println("Task executed by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 模拟耗时任务
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 程序结束时关闭线程池
        Runtime.getRuntime().addShutdownHook(new Thread(example::shutdown));
    }
}

优点:

  • 限制并发:通过 Semaphore 限制同时执行的任务数量。

  • 线程复用:线程池中的线程会复用,减少线程创建和销毁的开销。

 4)任务分批处理

如果任务数据量很大,可以将任务分批处理,每批任务由一个线程处理。

示例代码:

import java.util.concurrent.*;

public class BatchTaskExample {
    private final ExecutorService threadPool = Executors.newFixedThreadPool(3);

    public void processTasks(List<Runnable> tasks) {
        int batchSize = tasks.size() / 3;
        for (int i = 0; i < 3; i++) {
            int start = i * batchSize;
            int end = (i == 2) ? tasks.size() : start + batchSize;
            threadPool.submit(() -> {
                for (int j = start; j < end; j++) {
                    tasks.get(j).run();
                }
            });
        }
    }

    public void shutdown() {
        threadPool.shutdown();
    }

    public static void main(String[] args) {
        BatchTaskExample example = new BatchTaskExample();
        List<Runnable> tasks = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            tasks.add(() -> {
                System.out.println("Task executed by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 模拟耗时任务
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }
        example.processTasks(tasks);

        // 程序结束时关闭线程池
        Runtime.getRuntime().addShutdownHook(new Thread(example::shutdown));
    }
}

优点:

  • 分批处理:将任务分批处理,避免任务重叠。

  • 线程复用:线程池中的线程会复用,减少线程创建和销毁的开销。

总结

根据你的需求,可以选择以下方案:

  1. 使用线程池:限制线程数量,避免线程过多导致的资源竞争。

  2. 任务队列:将任务放入队列,按顺序执行,避免任务重叠。

  3. 限制并发任务数量:通过 Semaphore 限制同时执行的任务数量。

  4. 任务分批处理:将任务分批处理,每批任务由一个线程处理。

选择合适的方案可以有效解决线程过多导致的问题,提高任务执行的效率。

2. 避免函数被重复调用

Java中,可以通过多种方式避免函数被重复调用。以下是常见的方法

1)使用标志位

函数调用前,设置一个标志位表示该函数是否已经被调用过。如果已经被调用多,则不再重复调用。

public class FunctionCall {
    private static boolean isCalled = false;

    public static void function() {
        if (isCalled) {
            System.out.println("Function is already being called.");
            return;
        }
        isCalled = true;
        try {
            // Function logic here
            System.out.println("Function is being called.");
        } finally {
            isCalled = false;
        }
    }

    public static void main(String[] args) {
        new Thread(() -> function()).start();
        new Thread(() -> function()).start();
    }
}

2)使用同步锁

使用同步锁(如 synchronizedReentrantLock)来确保函数在同一时间只能被一个线程调用。

import java.util.concurrent.locks.ReentrantLock;

public class FunctionCall {
    private static final ReentrantLock lock = new ReentrantLock();

    public static void function() {
        lock.lock();
        try {
            // Function logic here
            System.out.println("Function is being called.");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(() -> function()).start();
        new Thread(() -> function()).start();
    }
}

3)使用AOP和缓存

通过AOP(面向切面编程)和缓存来检测函数是否正在被调用。如果函数正在被调用,则不再重复调用。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;

@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("functionCache");
    }
}

@Aspect
public class FunctionCallAspect {
    private final CacheManager cacheManager;

    public FunctionCallAspect(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    @Pointcut("execution(* com.example.FunctionCall.function(..))")
    public void functionCallPointcut() {}

    @Around("functionCallPointcut()")
    public Object aroundFunctionCall(ProceedingJoinPoint joinPoint) throws Throwable {
        Cache cache = cacheManager.getCache("functionCache");
        if (cache != null && cache.get("functionKey") != null) {
            System.out.println("Function is already being called.");
            return null;
        }
        cache.put("functionKey", "inProgress");
        try {
            return joinPoint.proceed();
        } finally {
            cache.evict("functionKey");
        }
    }
}

public class FunctionCall {
    public static void function() {
        // Function logic here
        System.out.println("Function is being called.");
    }

    public static void main(String[] args) {
        new Thread(() -> function()).start();
        new Thread(() -> function()).start();
    }
}

4)使用消息队列

将函数调用请求放入消息队列中,然后由后台服务从队列中取出并处理,避免了函数的重复调用。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class FunctionCall {
    private static final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

    public static void function() {
        queue.add(() -> {
            // Function logic here
            System.out.println("Function is being called.");
        });
    }

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                try {
                    Runnable task = queue.take();
                    task.run();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();

        new Thread(() -> function()).start();
        new Thread(() -> function()).start();
    }
}

5)使用限流算法

通过限流算法(如漏桶算法或令牌桶算法)来控制函数的调用频率,避免函数的重复调用。

import java.util.concurrent.Semaphore;

public class FunctionCall {
    private static final Semaphore semaphore = new Semaphore(1);

    public static void function() {
        try {
            semaphore.acquire();
            // Function logic here
            System.out.println("Function is being called.");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            semaphore.release();
        }
    }

    public static void main(String[] args) {
        new Thread(() -> function()).start();
        new Thread(() -> function()).start();
    }
}

持续更新ing,推荐博客:

  • Java 面试指南 | JavaGuide

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2284309.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全程Kali linux---CTFshow misc入门

图片篇(基础操作) 第一题&#xff1a; ctfshow{22f1fb91fc4169f1c9411ce632a0ed8d} 第二题 解压完成后看到PNG&#xff0c;可以知道这是一张图片&#xff0c;使用mv命令或者直接右键重命名&#xff0c;修改扩展名为“PNG”即可得到flag。 ctfshow{6f66202f21ad22a2a19520cdd…

深度学习笔记——循环神经网络之LSTM

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍面试过程中可能遇到的循环神经网络LSTM知识点。 文章目录 文本特征提取的方法1. 基础方法1.1 词袋模型&#xff08;Bag of Words, BOW&#xff09;工作…

[MILP] Logical Constraints 0-1 (Note2)

1. 如果选择了项目1&#xff0c;则项目2&#xff0c;3也要求被选中 表示为&#xff1a; 2. 如果确定了选项目1&#xff0c;则接下来必须选项目2或者项目3 表示为&#xff1a; or 3. 如果同时选择了项目2和项目3&#xff0c;则不可以选择项目1 表示为&#xff1a; 4. 如果…

DFFormer实战:使用DFFormer实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

如何复现o1模型,打造医疗 o1?

如何复现o1模型&#xff0c;打造医疗 o1&#xff1f; o1 树搜索一、起点&#xff1a;预训练规模触顶与「推理阶段&#xff08;Test-Time&#xff09;扩展」的动机二、Test-Time 扩展的核心思路与常见手段1. Proposer & Verifier 统一视角方法1&#xff1a;纯 Inference Sca…

【JavaWeb06】Tomcat基础入门:架构理解与基本配置指南

文章目录 &#x1f30d;一. WEB 开发❄️1. 介绍 ❄️2. BS 与 CS 开发介绍 ❄️3. JavaWeb 服务软件 &#x1f30d;二. Tomcat❄️1. Tomcat 下载和安装 ❄️2. Tomcat 启动 ❄️3. Tomcat 启动故障排除 ❄️4. Tomcat 服务中部署 WEB 应用 ❄️5. 浏览器访问 Web 服务过程详…

【NOI】C++程序结构入门之循环结构三-计数求和

文章目录 前言一、计数求和1.导入2.计数器3.累加器 二、例题讲解问题&#xff1a;1741 - 求出1~n中满足条件的数的个数和总和&#xff1f;问题&#xff1a;1002. 编程求解123...n问题&#xff1a;1004. 编程求1 * 2 * 3*...*n问题&#xff1a;1014. 编程求11/21/3...1/n问题&am…

新项目上传gitlab

Git global setup git config --global user.name “FUFANGYU” git config --global user.email “fyfucnic.cn” Create a new repository git clone gitgit.dev.arp.cn:casDs/sawrd.git cd sawrd touch README.md git add README.md git commit -m “add README” git push…

【异步编程基础】FutureTask基本原理与异步阻塞问题

文章目录 一、FutureTask 的桥梁作用二、Future 模式与异步回调三、 FutureTask获取异步结果的逻辑1. 获取异步执行结果的步骤2. 举例说明3. FutureTask的异步阻塞问题 Runnable 用于定义无返回值的任务&#xff0c;而 Callable 用于定义有返回值的任务。然而&#xff0c;Calla…

二叉树高频题目——下——不含树型dp

一&#xff0c;普通二叉树上寻找两个节点的最近的公共祖先 1&#xff0c;介绍 LCA&#xff08;Lowest Common Ancestor&#xff0c;最近公共祖先&#xff09;是二叉树中经常讨论的一个问题。给定二叉树中的两个节点&#xff0c;它的LCA是指这两个节点的最低&#xff08;最深&…

vue事件总线(原理、优缺点)

目录 一、原理二、使用方法三、优缺点优点缺点 四、使用注意事项具体代码参考&#xff1a; 一、原理 在Vue中&#xff0c;事件总线&#xff08;Event Bus&#xff09;是一种可实现任意组件间通信的通信方式。 要实现这个功能必须满足两点要求&#xff1a; &#xff08;1&#…

音频入门(一):音频基础知识与分类的基本流程

音频信号和图像信号在做分类时的基本流程类似&#xff0c;区别就在于预处理部分存在不同&#xff1b;本文简单介绍了下音频处理的方法&#xff0c;以及利用深度学习模型分类的基本流程。 目录 一、音频信号简介 1. 什么是音频信号 2. 音频信号长什么样 二、音频的深度学习分…

Redis --- 分布式锁的使用

我们在上篇博客高并发处理 --- 超卖问题一人一单解决方案讲述了两种锁解决业务的使用方法&#xff0c;但是这样不能让锁跨JVM也就是跨进程去使用&#xff0c;只能适用在单体项目中如下图&#xff1a; 为了解决这种场景&#xff0c;我们就需要用一个锁监视器对全部集群进行监视…

使用shell命令安装virtualbox的虚拟机并导出到vagrant的Box

0. 安装virtualbox and vagrant [rootolx79vagrant ~]# cat /etc/resolv.conf #search 114.114.114.114 nameserver 180.76.76.76-- install VirtualBox yum install oraclelinux-developer-release-* wget https://yum.oracle.com/RPM-GPG-KEY-oracle-ol7 -O /etc/pki/rpm-g…

2025数学建模美赛|赛题翻译|E题

2025数学建模美赛&#xff0c;E题赛题翻译 更多美赛内容持续更新中...

SpringBoot统一数据返回格式 统一异常处理

统一数据返回格式 & 统一异常处理 1. 统一数据返回格式1.1 快速入门1.2 存在问题1.3 案列代码修改1.4 优点 2. 统一异常处理 1. 统一数据返回格式 强制登录案例中,我们共做了两部分⼯作 通过Session来判断⽤⼾是否登录对后端返回数据进⾏封装,告知前端处理的结果 回顾 后…

C语言学习强化

前言 数据的逻辑结构包括&#xff1a; 常见数据结构&#xff1a; 线性结构&#xff1a;数组、链表、队列、栈 树形结构&#xff1a;树、堆 图形结构&#xff1a;图 一、链表 链表是物理位置不连续&#xff0c;逻辑位置连续 链表的特点&#xff1a; 1.链表没有固定的长度…

反馈驱动、上下文学习、多语言检索增强等 | Big Model Weekly 第55期

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 01 A Bayesian Approach to Harnessing the Power of LLMs in Authorship Attribution 传统方法严重依赖手动特征&#xff0c;无法捕捉长距离相关性&#xff0c;限制了其有效性。最近的研究利用预训练语言模型的…

git reset (取消暂存,保留工作区修改)

出现这种情况的背景&#xff1a;我不小心把node_modules文件添加到暂存区了&#xff0c;由于文件过大&#xff0c;导致不能提交&#xff0c;所以我想恢复之前的状态&#xff0c;但又不想把修改的代码恢复为之前的状态&#xff0c;所以使用这个命令可以只恢复暂存区的状态&#…

Coze插件开发之基于已有服务创建并上架到扣子商店

Coze插件开发之基于已有服务创建并上架到扣子商店 在应用开发中&#xff0c;需要调用各种插件&#xff0c;以快速进行开发。但有时需要调用的插件在扣子商店里没有&#xff0c;那怎么办呢&#xff1f; 今天就来带大家快速基于已有服务创建一个新的插件 简单来讲&#xff0c;就是…