一、多线程基础知识

本文深入探讨了程序、进程、线程和协程的概念,指出进程是资源分配单位,线程是调度执行单位,而协程更轻量化。在设置线程数量时,考虑了IO密集型和CPU密集型应用的差异,并介绍了如何根据阻塞系数估算线程数。此外,文章还讨论了Java中启动线程的五种方法以及线程的六种状态和中断机制。最后,提出了避免使用已废弃的线程控制方法,推荐使用中断机制来优雅地结束线程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#程序、进程、线程、纤程

  • 什么是程序
    • 可执行文件,例如(QQ.EXE)
  • 什么是进程
    • 进程是资源的分配的基本单位
  • 什么是线程
    • 线程是调度执行的基本单位
    • 多个线程共享同一个进程的资源
    • 线程的切换是需要消耗资源的
  • 什么是协程/纤程
    • 是一种最轻量化的线程(lightweight threads)。它是一种用户线程(user thread),让应用程序可以独立决定自己的线程要如何运作。操作系统内核不能看见它,也不会为它进行调度。就像一般的线程,纤程有自己的寻址空间。但是纤程采取合作式多任务(Cooperative multitasking),而线程采取先占式多任务(Pre-emptive multitasking)。应用程序可以在一个线程环境中创建多个纤程,然后手动运行它。纤程不会被自动运行,必须要由应用程序自已指定让它运行,或换到下一个纤程。

工作中如何设置有效的线程数量

  • 根据压测,测试出适合的线程数量
  • 在Java Concurrency in Practice一书中给出了估算线程池大小的公式:
    在这里插入图片描述
    • 关于等待时间可以使用profiler这类的系统时间统计工具(例如:Jprofiler、arthas)
  • 针对IO密集型的Cpu进行线程池的估计
    • 目前按照我看过的一些开源框架,线程池中的现线程基本就是IO密集型(2n +1 ),CPU密集型设置为 n + 1。但实际情况往往复杂的多,不会按照这个进行设置,进行这种设置,通常是框架层面,例如netty,dubbo这种底层通讯框架会参考这样的标准去设置,在实际业务中往往不会这样做。
    • 对于IO密集型网上还有一个公式:

线程数 = CPU核心数/(1-阻塞系数)
这个阻塞系数一般为0.8~0.9之间,也可以取0.8或者0.9。

  • 我觉这个公式有一定的道理,考虑了阻塞的概念。

    • 首先在我们业务开发中,基本上都是IO密集型,因为往往都会去操作数据库,操作redis,es等存储型组件,设涉及磁盘IO,网络IO,那什么场景下是CPU密集型呢?纯计算类,例如计算圆周率的位数,当然我们基本接触不到。

    • IO密集型,多一些线程,主要是可以增加IO的并发度,CPU密集型不宜过多线程是会造成线程切换,浪费时间。

  • 接下来我们以一个实际的场景来说明如何设置线程数量。

    • 一个4C8G的机器上部署了一个MQ消费者,用来消费MQ消息的,在RocketMQ的实现中,消费端也是用一个线程池来消费线程的,那这个线程数要怎么设置呢?

    • 首先如果按照 2n + 1 的公司来做,线程数设置为 9个,但我们会发现,如果增大线程数量,会显著提高消息的处理能力,说明 2n + 1 对于业务场景来说,不太合适。

    • 这个时候套用 线程数 = CPU核心数/(1-阻塞系数) 阻塞系数取 0.8 ,线程数为为 20 。阻塞系数取 0.9,大概线程数40,20个线程数我觉得可以。

    • 如果我们发现数据库的操作耗时比较多,此时可以继续提高阻塞系数,从而增大线程数量。

java中启动线程的5种方法

public class HowToCreateThread {
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello MyThread!");
        }
    }

    static class MyRun implements Runnable {
        @Override
        public void run() {
            System.out.println("Hello MyRun!");
        }
    }

    static class MyCall implements Callable<String> {
        @Override
        public String call() {
            System.out.println("Hello MyCall");
            return "success";
        }
    }

    //启动线程的5种方式
    public static void main(String[] args) throws Exception {
        
        new MyThread().start();     //@1
        new Thread(new MyRun()).start(); //@2
        new Thread(() -> {
            System.out.println("Hello Lambda!");   //@3
        }).start();

        FutureTask<String> task = new FutureTask<>(new MyCall());  //@4
        Thread t = new Thread(task);
        t.start();
        System.out.println(task.get());

        ExecutorService service = Executors.newCachedThreadPool();  //@5
        service.execute(() -> {
            System.out.println("Hello ThreadPool");
        });

        Future<String> f = service.submit(new MyCall());
        String s = f.get();
        System.out.println(s);
        service.shutdown();

    }

}

  • @1 继承thread方法
  • @2 实现runnable方法
    • 实现比继承好,java的多态:单继承多实现
  • @3 lambda表达式实现
  • @4 使用Callable带返回值的任务执行
  • @5 使用线程池

java的6种线程状态

  1. NEW: 线程刚刚创建,还没有启动
  2. RUNABLE: 可运行状态,由线程调度器可以安排执行
  3. WAITING: 等待被唤醒
  4. TIMED WAITING: 隔一段时间后自动唤醒
  5. BLOCKED: 被阻塞,正在等待锁
  6. TERMINATED: 线程结束

在这里插入图片描述

线程的中断机制

在Thread类中,提供了stop(),suspend()和resume()方法,这三个方法分别是用来结束,暂停,恢复线程. 但是都已经被标记为@Deprecated废弃了. 因为一个线程不应该由其他线程来结束,他应该收到别人的通知,然后自己在合适的位置结束,如果不合理的结束,会导致很多意外的结果,比如临界区还没完全操作完,提前释放锁,但是部分状态已经改变,还有没有做一些清理操作等等.

基于上面的理由,Java提供了新的中断机制(interrupt),其他线程调用想要终止线程的interrupt()方法. 这个时候线程会根据自己的状态做出响应:

  • 如果线程处于阻塞状态(sleep,wait,join),则线程会抛出InterruptedException异常.
  • 如果线程处于正常运行状态,则还是正常运行,但是中断的标志被设置为true,相当于有人通知 你该结束自己了.

以下是Thread中提供中断的三个方法:

  • java.lang.Thread#interrupt
    • 打断某个线程(设置标记位)
  • java.lang.Thread#interrupted
    • 查询当前线程是否被打断过,并重置打断标志
  • java.lang.Thread#isInterrupted()
    • 查询某个线程是否被打断过(查询标志位)

如何结束一个线程

  • 使用stop方法(已经废弃)
    • 直接释放锁,可能存在数据不一致问题
  • suspend(暂停)resume(恢复执行)(已经废弃)
    • 直接释放锁,可能存在数据不一致问题
  • 使用volatile关键字
    • 自定义标志位退出条件
  • 使用interrupt 中断

欢迎大家关注我的微信公众号共同学习进步:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值