多线程与高并发——并发编程(5)

这篇具有很好参考价值的文章主要介绍了多线程与高并发——并发编程(5)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

五、线程池

1 什么是线程池

为什么要使用线程池?

  • 在开发中,为了提升效率,我们需要将一些业务采用多线程的方式去执行。比如,有一个比较大的任务,可以将任务分成几块,分别交给几个线程去执行,最终做一个汇总即可。再比如,做业务操作时,需要发送短信或邮件,这些操作也可以基于异步的方式完成,这种异步的方式,其实就是在构建一个线程去执行。
  • 但是,如果每次异步操作或者多线程操作都需要新创建一个线程,使用完毕后,线程再被销毁。这样的话,会对系统造成一些额外的开销。在处理过程中,到底有多少线程处理了多少任务,以及每个线程的开销也无法统计和管理,所以我们需要一个线程池机制来管理这些内容。
  • 线程池的概念和连接池类似,就是在一个Java的集合中存储大量的线程对象,每次需要执行异步操作或者多线程操作时,不需要重新创建线程,直接从集合中拿到线程对象直接执行方法就可以了。
  • JDK中提供了线程池的类。在线程池构建的初期,可以将任务提交到线程池中,会根据一定的机制来异步执行这个任务。
    • 可能任务直接被执行;
    • 任务可以暂存起来,等到有空闲线程再来处理;
    • 任务也可能被拒绝,无法被执行。
  • JDK提供的线程池记录了每个线程处理了多少个任务,以及整个线程池处理了多少个任务。同时还可以针对任务执行前后做一些钩子函数的实现,可以在任务执行前后做一些日志信息,这样可以多记录信息方便后面统计线程池执行任务的一些内容参数等。

2 JDK自带的构建线程池的方式

JDK中基于Executors 提供了很多线程池

2.1 FixedThreadPool

  • 这个线程池的线程数是固定的,在 Executors 中第一个方法就是构建 FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
   
    return new ThreadPoolExecutor(nThreads, nThreads,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>());
}
  • 构建时,需要给newFixedThreadPool方法提供一个 nThreads 的属性,而这个属性其实就是当前线程池中线程的个数。当前线程池的本质其实就是使用 ThreadPoolExecutor。
  • 构建好当前线程池后,线程个数已经固定好(线程是懒加载的,在构建之初,线程并没有构建出来,而是随着任务的提交才会将线程在线程池中构建出来)。如果线程没构建,线程会等着任务执行时被创建和执行;如果线程都已经构建好了,此时任务会被放到 LinkedBlockingQueue 无界队列中存放,等待线程从 LinkedBlockingQueue 中去 take 出任务,然后执行。
  • 测试代码:
public static void main(String[] args) {
   
    ExecutorService threadPool = Executors.newFixedThreadPool(3);
    threadPool.execute(() -> {
   
        System.out.println("1号任务" + Thread.currentThread().getName() + System.currentTimeMillis());
        try {
   
            Thread.sleep(5000);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    });
    threadPool.execute(() -> {
   
        System.out.println("2号任务" + Thread.currentThread().getName() + System.currentTimeMillis());
        try {
   
            Thread.sleep(5000);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    });
    threadPool.execute(() -> {
   
        System.out.println("3号任务" + Thread.currentThread().getName() + System.currentTimeMillis());
        try {
   
            Thread.sleep(5000);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    });
}

2.2 SingleThreadExecutor

  • 这个线程池看名字就知道是单例线程池,线程池中只有一个工作线程在处理任务。如果业务涉及到顺序消费,可以采用 SigleThreadExecutor。
public static ExecutorService newSingleThreadExecutor() {
   
	// 在内部依然是构建了ThreadPoolExecutor,设置线程个数为1。当任务投递过来后,第一个任务会被工作线程处理,后续的任务会被扔到阻塞队列中,投递到阻塞队列中任务的顺序就是工作线程处理的顺序
	// 当前线程池可以用作顺序处理的一些业务中
    return new Executors.FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>()));
}

static class FinalizableDelegatedExecutorService extends Executors.DelegatedExecutorService {
   
  	// 线程池的使用没有区别,跟正常的ThreadPoolExecutor没区别
	FinalizableDelegatedExecutorService(ExecutorService executor) {
   
        super(executor);
    }
	// finalize是当前对象被GC干掉之前要执行的方法
	// 当前FinalizableDelegatedExecutorService的目的是为了在当前线程池被GC回收之前,可以执行shutdown,shutdown方法是将当前线程池停止,并且干掉工作线程
	// 但是不能基于这种方式保证线程池一定会执行shutdown,finalize在执行时,是守护线程,这种线程无法保证一定可以执行完毕。
	// 在使用线程池时,如果线程池是基于一个业务构建的,在使用完毕之后,一定要手动执行shutdown,否则会造成JVM中一堆线程
    protected void finalize() {
   
        super.shutdown();
    }
}
  • 测试代码:
public static void main(String[] args) {
   
    ExecutorService threadPool = Executors.newSingleThreadExecutor();

    threadPool.execute(() -> {
   
        System.out.println(Thread.currentThread().getName() + "," + "111");
    });
    threadPool.execute(() -> {
   
        System.out.println(Thread.currentThread().getName() + "," + "222");
    });
    threadPool.execute(() -> {
   
        System.out.println(Thread.currentThread().getName() + "," + "333");
    });
    threadPool.execute(() -> {
   
        System.out.println(Thread.currentThread().getName() + "," + "444");
    });
}
  • 测试线程池使用完毕后,不执行 shutdown 的后果:
    • 如果是局部变量,仅限当前线程使用的线程池,在使用完毕之后要记得执行 shutdown,避免线程无法结束。
    • 如果是全局的线程池,很多业务都会用到,使用完毕之后不用 shutdown,因为其他业务也要执行当前线程池。

多线程与高并发——并发编程(5),Java多线程并发编程,并发编程,线程池

2.3 CachedThreaPool

  • 看名字好像是一个缓存的线程池
public static ExecutorService newCachedThreadPool() {
   
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
            60L, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>());
}
  • 当第一次提交任务到线程池时,会直接构建一个工作线程,这个工作线程执行完之后,如果60秒没有任务可以执行,那么会结束;如果等待 60 秒期间有任务进来,它会再次拿到这个任务去执行。如果后续提升任务时,没有线程是空闲的,那么久构建工作线程去执行。
  • 最大的一个特点:任务只要提交到当前的 CachedThreaPool 中,就必然有工作线程可以处理。
  • 测试代码:
public static void main(String[] args) {
   
    ExecutorService threadPool = Executors.newCachedThreadPool();
    for (int i = 1; i <= 200; i++) {
   
        final int x = i;
        threadPool.execute(() -> {
   
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + x);
        });
    }
}

2.4 ScheduleThreadPool

  • 看名字可以猜到当前线程池是一个定时任务的线程池,而这个线程池就是可以以一定周期去执行一个任务,或者延迟多久执行一个任务一次。
// 基于这个方法可以看到,构建的是 ScheduledThreadPoolExecutor
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
   
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {
   
	// ...
}
  • 本质上还是ThreadPoolExecutor,只不是在原来的线程池基础上实现了定时任务的功能。原理是基于 DelayQueue 实现的延迟执行,周期性执行是任务执行完毕后,再次扔回到阻塞队列中。
  • 测试代码:
public static void main(String[] args) {
   
    ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
//        // 正常执行
//        threadPool.execute(() -> {
   
//            System.out.println(Thread.currentThread().getName() + ": 1");
//        });
//
//        // 延迟执行,执行当前任务延迟5s后执行
//        threadPool.schedule(() -> {
   
//            System.out.println(Thread.currentThread().getName() + ": 2");
//        }, 5, TimeUnit.SECONDS);
//
//        // 周期执行,当前任务第一次延迟2s执行,然后每1s执行一次
//        // 这个方法在计算下次执行时间时,是从任务刚刚开始时就计算
//        threadPool.scheduleAtFixedRate(() -> {
   
//            try {
   
//                Thread.sleep(3000);
//            } catch (InterruptedException e) {
   
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName() + ": 3");
//        }, 2, 1, TimeUnit.SECONDS);

    threadPool.scheduleWithFixedDelay(() -> {
   
        try {
   
            Thread.sleep(3000);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ": 4");
    }, 2, 1, TimeUnit.SECONDS);
}

2.5 WorkStealingPool

  • 当前JDK提供的构建线程池的方式 newWorkStealingPool 和之前的线程池有非常大的区别,之前定长、单例、缓存、定时任务都是基于 ThreadPoolExecutor 去实现的,而 WorkStealingPool 是基于 ForkJoinPool 构建的。
  • ThreadPoolExecutor的核心点:只有一个阻塞队列存放当前任务
  • ForkJoinPool的核心点:当有一个特别大的任务时,采用 ThreadPoolExecutor 只会有一个线程去执行,但是 ForkJoinPool 会将一个大任务拆分成很大小任务,放到当前线程的阻塞队列中,其它空闲线程就可以去处理有任务的线程的阻塞队列中的任务

多线程与高并发——并发编程(5),Java多线程并发编程,并发编程,线程池

  • 核心就是希望没有工作线程处于空闲状态,每个线程都使用率满满

举个例子:来一个比较大的数组,里面存满值,计算总和文章来源地址https://www.toymoban.com/news/detail-702805.html

  • 单线程处理:
/** 非常大的数组 */
static int[] nums = new int[1_000_000_000];
// 填充值
static{
   
    for (int i = 0; i < nums.length; i++) {
   
        nums[i] = (int) ((Math.random()) * 1000);
    }
}
public static void main(String[] args) {
   
    // ===================单线程累加10亿数据================================
    System.out.println("单线程计算数组总和!");
    long start = System.nanoTime();
    int sum = 0;
    for (int num : nums) {
   
        sum += num;
    }
    long end = System.nanoTime();
    System.out.println("单线程运算结果为:" + sum + ",计算时间为:" + (end  - start));
}
  • 多线程分而治之方式处理:
/**
 * 非常大的数组
 */
static int[] nums = new int[1_000_000_000];
// 填充值
static {
   
    for (int i = 0; i < nums.length; i++) {
   
        nums[i] = (int) ((Math.random()) * 1000);
    }
}
public static void main(String[] args) {
   
    // ===================单线程累加10亿数据================================
    System.out.println("单

到了这里,关于多线程与高并发——并发编程(5)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Java|多线程与高并发】定时器(Timer)详解

    在Java中,定时器 Timer 类是用于执行定时任务的工具类。它允许你安排一个任务在未来的某个时间点执行,或者以固定的时间间隔重复执行。 在服务器开发中,客户端向服务器发送请求,然后等待服务器响应. 但服务器什么时候返回响应,并不确定. 但也不能让客户端一直等下去

    2024年02月07日
    浏览(46)
  • 【Java|多线程与高并发】wait和notify方法详解

    在Java多线程环境中,线程之间是抢占式执行的,线程的调度是随机的.这就很难受了. 在很多情况下我们希望线程以我们想要的顺序来执行. 这就需要 wait 和 notify 这两个方法 首先是 wait 方法 wait是 Object 类的方法,而Java中的类都是间接或直接继承于Object类. 因此只要是类的实例都可

    2024年02月10日
    浏览(56)
  • 【Java|多线程与高并发】JUC中常用的类和接口

    JUC是Java并发编程中的一个重要模块,全称为 Java Util Concurrent (Java并发工具包),它提供了一组用于多线程编程的工具类和框架,帮助开发者更方便地编写线程安全的并发代码。 本文主要介绍 Java Util Concurrent 下的一些常用接口和类 Callable接口类似于Runnable. 有一点区别就是

    2024年02月12日
    浏览(33)
  • 【Java|多线程与高并发】设计模式-单例模式(饿汉式,懒汉式和静态内部类)

    设计模式是一种在软件开发中常用的解决复杂问题的方法论。它提供了一套经过验证的解决方案,用于解决特定类型问题的设计和实现。设计模式可以帮助开发人员提高代码的可重用性、可维护性和可扩展性。 设计模式有很多,本文主要介绍单例模式. 单例模式是一种创建型设

    2024年02月11日
    浏览(54)
  • 多线程与高并发--------线程池

    在开发中,为了提升效率的操作,我们需要将一些业务采用多线程的方式去执行。 比如有一个比较大的任务,可以将任务分成几块,分别交给几个线程去执行,最终做一个汇总就可以了。 比如做业务操作时,需要发送短信或者是发送邮件,这种操作也可以基于异步的方式完

    2024年02月13日
    浏览(40)
  • 多线程与高并发--------阻塞队列

    1.1 生产者消费者概念 生产者消费者是设计模式的一种。让生产者和消费者基于一个容器来解决强耦合问题。 生产者 消费者彼此之间不会直接通讯的,而是通过一个容器(队列)进行通讯。 所以生产者生产完数据后扔到容器中,不通用等待消费者来处理。 消费者不需要去找

    2024年02月13日
    浏览(37)
  • Java并发编程面试题——线程池

    参考文章: 《Java 并发编程的艺术》 7000 字 + 24 张图带你彻底弄懂线程池 (1) 线程池 (ThreadPool) 是一种用于 管理和复用线程的机制 ,它是在程序启动时就预先创建一定数量的线程,将这些线程放入一个池中,并对它们进行有效的管理和复用,从而在需要执行任务时,可以从

    2024年02月07日
    浏览(51)
  • Java并发编程之线程池详解

    目录 🐳今日良言:不悲伤 不彷徨 有风听风 有雨看雨 🐇一、简介 🐇二、相关代码 🐼1.线程池代码 🐼2.自定义实现线程池 🐇三、ThreadPoolExecutor类 首先来介绍一下什么是线程池,线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程

    2024年02月12日
    浏览(46)
  • 【Java 并发编程】一文读懂线程、协程、守护线程

    在 Java 线程的生命周期一文中提到了 就绪状态的线程在获得 CPU 时间片后变为运行中状态 ,否则就会在 可运行状态 或者 阻塞状态 ,那么系统是如何分配线程时间片以及实现线程的调度的呢?下面我们就来讲讲线程的调度策略。 线程调度是指系统为线程分配 CPU 执行时间片

    2023年04月08日
    浏览(60)
  • 【Java并发编程】变量的线程安全分析

    1.成员变量和静态变量是否线程安全? 如果他们没有共享,则线程安全 如果被共享: 只有读操作,则线程安全 有写操作,则这段代码是临界区,需要考虑线程安全 2.局部变量是否线程安全 局部变量是线程安全的 当局部变量引用的对象则未必 如果给i对象没有逃离方法的作用

    2024年02月08日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包