并发工具包与线程连接池

        并发是伴随着多核处理器的诞生而“席卷大地”的,为了充分利用硬件资源,诞生了多线程技术。但是多线程又存在资源竞争的问题,引发了同步和互斥的问题,JDK1.5推出的java.util.concurrent(简称JUC)并发工具包,就是专门来解决这些问题的。


new Thread的弊端:

  • new Thread新建对象,性能差。

  • 线程缺乏统一的管理,可能无限制的新建线程,相互竞争,严重时会占用过多系统资源或OOM(内存溢出)。

ThreadPool线程池:

  • 重用存在的线程,减少对象新建、消亡的开销。

  • 线程总数可控,提高资源的利用率。

  • 避免过多资源竞争,避免阻塞。

  • 提供额外功能,定时执行、定期执行,监控等。


JUC提供了调度器对象Executors来创建线程池,可创建的线程池有四种:

①CachedThreadPool可缓存线程池

特点: 1.如果线程池中无空闲线程,则创建;有,则利用起来。

          2.线程总数固定。

          3.可以进行自动线程回收。

②FixedThreadPool定长线程池

特点: 1.如果线程池中有空闲线程,则利用起来;如果线程池中的所有线程都在执行任务,那么后续任务进入等待状态;
             直到有空闲的线程了,该线程再执行后续任务。

         2.无线程数上限。

注:如果有多个后续任务的话,那么空闲下来的线程会根据FIFO原则,来选择要执行哪一个线程。
        (FIFO:即First Input First Output,先入先出;还有LIFI:即Last In First Out后入先出)

注:Java并发类型,可以分为两种:计算密集型(即:线程中大部分时间用来进行计算)和IO密集型(即:线程中大部
        分时间用来进行IO)。如果是计算密集型的并发,那么线程池的(核心线程数)大小一般设置为N+1,如果是IO密
        集型的并发,那么线程池的(核心线程数)大小一般设置为2N+1。其中N为电脑核数。但是在实际使用时,线程
        池的大小往往没那么小,至于实际怎么设置线程池的大小可参考这里

③SingleThreadExecutor单线程线程池

说明: 用来模拟单线程的线程池

④ScheduledThreadPool调度线程池

说明: 一般用于处理定时任务;与其类似的还有Timer;在实际做项目时,我们既不用ScheduledThreadPool,
         也不用Timer;而是用成熟的定时任务框架Quartz或Spring自带的定时调度。

注:Quartz或Spring定时调度的具体用法,可详见我的这篇博客
        https://blog.csdn.net/justry_deng/article/details/80666508


四种线程池的创建使用示例:

CachedThreadPool可缓存线程池,使用示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * CachedThreadPool的创建
 *
 * @author JustryDeng
 * @date 2018/10/11 18:47
 */
public class ExecutorsCreateCachedThreadPool {
    private static Integer count = 10000;
    private static final Object OBJ= new Object();
    public static void main(String[] args) throws InterruptedException {

        // -> 创建可缓存线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 1; i <= 10000; i++) {
            // 使用lambda表达式简单实现Runnable接口的run方法
            executorService.execute(() -> {
                // 只有获得了object的锁的线程,才能操作
                synchronized (OBJ) {
                    count--;
                }
            });
        }
        // 当线程池中所有线程都运行完毕后,关闭线程池
        executorService.shutdown();
        // 主线程阻塞2秒再输出count的值,为了避免输出打印count的值时,其余线程还没计算完;导致输出的不是count的最终值
        Thread.sleep(2000);
        System.out.println(count);
    }
}

提示在程序运行着的前提下,一个线程的消亡,(一般)不会影响到另一个线程的生命周期本人的示例中,由
           于main线程消亡后,程序就停止了运行,进而导致其他线程就无法完成自己完整的生命周期,所以本人
            的示例中,为了不让程序过早停止运行,于是就主动让main线程sleep了一会儿。
            在后面的博文中不再作相关解释

注:其中Executors.newCachedThreadPool()方法的源码为:

FixedThreadPool定长线程池,使用示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * FixedThreadPool的创建
 *
 * @author JustryDeng
 * @date 2018/10/11 18:47
 */
public class ExecutorsCreateFixedThreadPool {
    private static Integer count = 10000;
    private static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {

        // -> 创建定长线程池(该线程池中总共存在200个线程)
        ExecutorService executorService = Executors.newFixedThreadPool(200);

        for (int i = 1; i <= 10000; i++) {
            // 使用lambda表达式简单实现Runnable接口的run方法
            executorService.execute(() -> {
                // 使用重入锁,保证线程安全同步
                lock.lock();
                try {
                    count--;
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }

            });
        }
        // 当线程池中所有线程(包括排着队的)都运行完毕后,关闭线程池
        executorService.shutdown();
        // 主线程阻塞2秒再输出count的值,为了避免输出打印count的值时,其余线程还没计算完;导致输出的不是count的最终值
        Thread.sleep(2000);
        System.out.println(count);
    }
}

注:其中Executors.newFixedThreadPool()方法的源码为:

SingleThreadExecutor单线程线程池,使用示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * SingleThreadExecutor的创建
 *
 * @author JustryDeng
 * @date 2018/10/11 18:47
 */
public class ExecutorsCreateSingleThreadExecutor {
    private static Integer count = 10000;
    public static void main(String[] args) throws InterruptedException {

        // -> 创建定单线程线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        for (int i = 1; i <= 10000; i++) {
            // 使用lambda表达式简单实现Runnable接口的run方法
            // 因为是单线程池,所以不需要任何其他操作,就能保证数据的安全准确性
            executorService.execute(() -> count--);
        }
        // 当线程池中所有线程(包括排着队的)都运行完毕后,关闭线程池
        executorService.shutdown();
        // 主线程阻塞2秒再输出count的值,为了避免输出打印count的值时,其余线程还没计算完;导致输出的不是count的最终值
        Thread.sleep(2000);
        System.out.println(count);
    }
}

注:其中Executors.newSingleThreadPool()方法的源码为:

ScheduledThreadPool调度线程池,使用示例:

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * ScheduledThreadPool的创建
 *
 * @author JustryDeng
 * @date 2018/10/11 18:47
 */
public class ExecutorsCreateScheduledThreadPool {
    public static void main(String[] args) {

        // -> 创建定时调度线程池(初始化核心线程数为5)
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

        // 使用lambda表达式简单实现Runnable接口的run方法
        System.out.println("当前时间是:" + new Date());
        scheduledExecutorService.schedule(() -> System.out.println("10秒后输出此语句! -> " + new Date()),10, TimeUnit.SECONDS);
        // 当线程池中所有线程(包括排着队的)都运行完毕后,关闭线程池
        scheduledExecutorService.shutdown();
    }
}

注:其中Executors.newSingleThreadPool()方法的源码为:


在阿里piapia规范里,线程池不允许使用Executors去创建:

        线程池不允许使用Executors去创建,而是通过主动创建ThreadPoolExecutor或ScheduledThreadPoolExecutor实例的方式去创建。这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

同时也给出了不推荐使用Executors创建线程池的原因:
◎newFixedThreadPool和newSingleThreadExecutor:
         主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

◎newCachedThreadPool和newScheduledThreadPool:
            主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

◎让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

阿里推荐用以下方式来创建线程池

声明:构造方法的各参数介绍,可详见https://blog.csdn.net/justry_deng/article/details/89331199

创建调度线程池ScheduledExecutorService:

需要引入依赖:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>

提示:SpringCloud项目,zuul等模块儿本身就依赖有commons-lang3依赖。

注:如果不想引入commons-lang3依赖,那么可以使用默认的线程工厂或者其它的线程工厂实现。

创建:

// 需要引入commons-lang3依赖
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory
                                                                              .Builder()
                                                                              .namingPattern("example-schedule-pool-%d")
                                                                              .daemon(true)
                                                                              .build());

创建公共线程池ExecutorService

需要引入依赖:

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>27.0.1-jre</version>
</dependency>

提示:SpringCloud项目,eureka、zuul等模块儿本身就依赖有guava依赖。

注:如果不想引入guava依赖,那么可以使用默认的线程工厂或者其它的线程工厂实现。

创建:

// ThreadFactoryBuilder需要引入guava依赖
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
// 创建Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 50, 3000L, TimeUnit.MILLISECONDS,
                                              new LinkedBlockingQueue<Runnable>(20), 
                                              namedThreadFactory, 
                                              new ThreadPoolExecutor.AbortPolicy());
// 执行任务
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown

使用示例:

/**
 * 虽然我们可以通过Excutors创建线程池,但是推荐:我们自己手动创建线程池
 *
 * @author JustryDeng
 * @date 2018/12/29 13:58
 */
public class CreateThreadPool {

    private static final Object OBJ = new Object();

    private static Integer count = 10000;

    public static void main(String[] args) {
        try {
            createThreadPoolTest();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void createThreadPoolTest() throws InterruptedException {
        int length = 10000;

        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("justry-deng-pool-%d").build();

        ExecutorService executorService = new ThreadPoolExecutor(5, 200,
                                                                 0L, TimeUnit.MILLISECONDS,
                                                                 new LinkedBlockingQueue<>(1024),
                                                                 namedThreadFactory,
                                                                 new ThreadPoolExecutor.AbortPolicy());
        for (int i = 1; i <= length; i++) {
            // 使用lambel表达式简单实现Runnable接口的run方法
            executorService.execute(() -> {
                // 只有获得了object的锁的线程,才能操作
                synchronized (OBJ) {
                    System.out.println(Thread.currentThread().getName());
                    count--;
                }
            });
        }
        // 当线程池中所有线程都运行完毕后,关闭线程池
        executorService.shutdown();
        // 主线程阻塞2秒再输出count的值,为了避免输出打印count的值时,其余线程还没计算完;导致输出的不是count的最终值
        Thread.sleep(2000);
        System.out.println(count);
    }

}

直接在spring配置文件中配置线程池

<bean id="userThreadPool"
    class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="10" />
    <property name="maxPoolSize" value="100" />
    <property name="queueCapacity" value="2000" />
    <property name="threadFactory" value= threadFactory />
    <property name="rejectedExecutionHandler">
        <ref local="rejectedExecutionHandler" />
    </property>
</bean>

 

声明:本文是学习笔记,主要学习自以下视频

^_^ 如有不当之处,欢迎指正

^_^ 学习视频
           《Java多线程与并发实战视频课程》,齐毅 

^_^ 多线程一栏所有测试示例代码,托管链接
               https://www.cnblogs.com/aheizi/p/6851416.html

^_^ 多线程一栏所有测试示例代码,托管链接
               https://github.com/JustryDeng/PublicRepository

^_^ 本文已经被收录进《程序员成长笔记(三)》,笔者JustryDeng

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值