线程池

并行与并发

并发:强调同一时间段

并行:强调同一时刻

 

具体实际情况还得看CPU数量

进程和线程的关系

进程是资源分配的最小单位,线程是CPU调度的最小单位;

一个进程至少包含一个线程;

 

举例:一个应用软件、一个浏览器、一个SpringBoot实例就是一个进程;每一个浏览器标签页就是一个线程,每一个下载任务就是一个线程;

 

 

线程的生命周期

线程的创建时间、线程的执行时间、线程的销毁时间

线程三大主要开销

  • Java 的线程模型是基于操作系统原生线程模型实现的,即是基于内核线程实现的;线程的创建,析构与同步都需要进行系统调用,在用户态和内核态切换开销大;
  • 每个线程都需要一个内核线程的支持,也就会消耗一定的内核栈空间,故能创建的线程数是有限的;
  • 若线程数量过多,则会导致频繁的上下文切换;

线程的状态

新建、 可运行(就绪/运行中)、 阻塞(请求获取锁)、 等待(io事件)、 定时等待(sleep)、 终止

创建线程

  • 线程调度是由系统控制的,程序员不可能精准的去干涉它;
  • 核心本质:new Thread()
class myThead extends Thread   { 
    // 重写run方法
    public void run()   { 
     // do something here  
    }  
 } 
 
public static void main(String[] args){
    myThead oneThread = new myThread();   
    // 启动线程:自动执行run方法(手动调用run方法则没有启动新线程)
    oneThread.start(); 
}
class SomeRunnable implements Runnable   { 
  // 重写run方法
  public void run()   { 
  //do something here  
  }  
} 
// 创建Runnable实现类的实例,并以此实例作为Thread的target对象,即该Thread对象才是真正的线程对象。
Runnable oneRunnable = new SomeRunnable();   
Thread oneThread = new Thread(oneRunnable);   
oneThread.start();

线程池提交任务与执行任务

线程池创建

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());

  • 阻塞队列一定是要有界的,否则会导致拒绝策略失效
  • 核心线程数:也称最小线程数
  • 最大空闲时间:针对的是超过核心线程数之后的线程 (如果现在核心线程数为10,但当前只有3个线程,即使超过了最大空闲时间,该线程也不会被销毁)

拒绝策略

  • CallerRunsPolicy - 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
  • AbortPolicy - 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
  • DiscardPolicy - 直接丢弃,其他啥都没有
  • DiscardOldestPolicy - 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入

线程池提交任务

// 方式一:execute 方法(无返回值、无法捕获异常)
public void execute(Runnable command) {
}

// 方式二:ExecutorService 中 submit 的三个方法(有返回值、可以捕获异常)
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

Demo实战

https://www.cnblogs.com/ReturnOfTheKing/p/17989174

 

线程安全

背景:多线程对统一资源竞争访问,解决方案就是加锁,必须确保使用同一个资源的多个线程共用一把锁

Synchronized(JDK5之前)

  • 用于锁住方法或者代码块
  • 隐式加锁:自动获得锁、自动释放锁
第一种写法:
synchronized (对象){ 
    // 需要被同步的代码
}

第二种写法:
权限修饰符 synchronized 返回值类型 方法名(形参列表){
    // 需要被同步的代码
}

 

ReentrantLock(JDK5之后)

  • 显示加锁,性能更好
  • 只能锁住代码块但不能锁住方法
public class Ticket implements Runnable {

    private final Lock lock = new ReentrantLock();
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            // 加锁
            this.lock.lock();
            try {
                if (this.tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖第" + this.tickets-- + "张票");
                }
            } finally {
                // 释放锁
                this.lock.unlock();
            }
        }
    }
}

 

volatile

注:线程在使用堆变量的时候会先将其拷贝一份到变量副本中,故可能存在数据不一致的情况。

  • volatile 关键字:强制线程在每次使用的时候,都会看一下共享区域最新的值。当然也可以使用加锁的方式替代该方法。
  • volatile 关键字只能保证每次使用共享数据的时候是最新值,但是不能保证原子性。

 

 

协同关系

  • wait会释放锁(Object类)、sleep不会释放锁(Thread类中声明的静态方法)
  • newCondition()返回线程的阻塞队列,引入了队列模型

 

 

 

Async注解

参考文章

作用

使加上该注解的类或方法能够异步执行任务

参数

value参数:指定线程池

注解无效的可能情况

  • 没有加@EnableAsync注解
  • @Async修饰的方法不是public方法
  • @Async修饰的方法被static修饰了
  • 调用方法和@Async方法在同一个类中
  • @Async修饰的方法的返回值不是void或Future

 

线程池和连接池的区别

  • MySQL连接池关注于对数据库资源(数据库连接对象)进行管理,而线程池关注于对多线程资源(线程对象和CPU等计算机资源)进行管理;

 

参考文章

【1】https://mp.weixin.qq.com/s/smfDcR-fjSlbvbvBXp1WMA

【2】https://www.cnblogs.com/snow-flower/p/6114765.html

【3】线程池参数配置

posted @   先娶国王后取经  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类
点击右上角即可分享
微信分享提示