java多线程及线程池使用

Java多线程

一、Java多线程涉及的包和类

  1. java.lang包,主要是线程基础类

            <1>Thread
            <2>Runnable
            <3>ThreadLocal
    
  2. Java.util.concurrent包(JUC),主要是一些线程基础以及并发工具包。 提供了很多有用的类,方便我们进行并发程序的开发

           有待更新!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    		<1>基础线程类
                 CallableFutureFutureTaskCompletableFuture
            <2>容器类(线程安全)
                 ArrayBlockQueue
                 LinkedBlockQueue
                 SynchronousQueue
                 PriorityBlockingQueue
                 DelayQueue 
                 ConcurrentHashMap 
                 CopyOnWriteArrayList 
                 CopyOnWriteArraySet 
            <3>锁类(java.util.concurrent.locks)
                Condition 
                Lock
                    ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock
                ReadWriteLock
                    ReentrantReadWriteLock
            <4>原子类(java.util.concurrent.atomic)
                AtomicBoolean 
                AtomicInteger 
                AtomicLong 
            <5>多线程控制类
                线程池:(接口)ExecutorExecutorService (类)ThreadPoolExecutorScheduledThreadPoolExecutor 
                线程池工具类: Executors
                并发控制器:Semaphore(信号量)CountDownLatch(倒数闩)CyclicBarrier(同步屏障)Exchanger(交换机)
    

二、Java创建多线程的方式

  1. 实现Runnable

  2. 继承Thread

  3. 实现Callable

  4. 线程池方式

    • java提供了构建线程池的工具,java.util.concurrent包中的Executors可以创建线程池。

      但是 阿里编码规范中不允许使用这种方式构建线程池,这种方式对线程的控制粒度比较低。eg:线程名字不能改、线程数固定等

    • java手动创建线程池 ThreadPoolExecutor

       new ThreadPoolExecutor(……)
      
    • Spring提供的构建线程池的工具 ThreadPoolTaskExecutor

三、Java线程池

1. 创建线程池ThreadPoolExecutor的7个参数

public ThreadPoolExecutor(int corePoolSize,	//核心线程数
                          int maximumPoolSize,	//最大线程数
                          long keepAliveTime,	//最大空闲时间
                          TimeUnit unit,	//时间单位
                          BlockingQueue<Runnable> workQueue,	//阻塞队列
                          ThreadFactory threadFactory,	//线程工厂
                          RejectedExecutionHandler handler) {	//拒绝策略
    ……
}
  • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
  • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式,可指定线程池名字等。
  • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

2. 线程池的执行流程

  1. 任务提交到线程池,若核心线程数有空闲,则执行;若核心线程数没有空闲,则任务要放到阻塞队列中。

  2. 若阻塞队列中没满,则放到队列中排队;若阻塞队列满了,则看工作线程数是否为最大线程数,

  3. 若没有达到最大线程数,则创建非核心线程数;若已经达到最大线程数,则采用拒接策略

    线程池执行流程图
    img
  • 为什么要先进阻塞再去尝试创建非核心线程:

    1. 在创建新线程的时候,是要获取全局锁的,这时候其他线程会被阻塞,影响整体效率。
    2. 在核心线程已满时,如果任务继续增加那么放在队列中,等队列满了而任务还在增加那么就要创建临时线程了,这样代价低。
  • 创建线程池时具体每个参数设多少值怎么定

    1. 跑任务看运行时间理解各个参数

    2. 创建线程池具体参数设置要看的变量有:每秒的任务数、每个任务花费的时间等等

3. 线程池的使用示例

1)用java原生的线程池类 ThreadPoolExecutor

/**
 * ThreadPoolExecutor使用示例
 * 1.构造一个线程池
 * 2.往线程池中提交任务
 * 3.关闭线程池
 *
 * @author zhangna
 */
public class ThreadPoolExecutorDemo {
    private static int produceTaskSleepTime = 5;
    private static int consumeTaskSleepTime = 5000;
    private static int produceTaskMaxNumber = 20;

    public static void main(String[] args) {
        //1.构造一个线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
                new ThreadPoolExecutor.DiscardPolicy());
        try {
            for (int i = 1; i <= produceTaskMaxNumber; i++) {
                String work = "work@ " + i;
                System.out.println("put :" + work);
                //2.往线程池中提交任务
                threadPool.execute(new ThreadPoolTask(work));
                Thread.sleep(produceTaskSleepTime);
            }

            //3.关闭线程池
            threadPool.shutdown();
            threadPool.awaitTermination(1, TimeUnit.HOURS);
            System.out.println("线程池中启动的任务已全部完成");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 线程池执行的任务
     **/
    public static class ThreadPoolTask implements Runnable, Serializable {
        private static final long serialVersionUID = 0;
        //保存任务所需要的数据
        private Object threadPoolTaskData;

        ThreadPoolTask(Object works) {
            this.threadPoolTaskData = works;
        }

        @Override
        public void run() {
            //处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句
            System.out.println("start------" + threadPoolTaskData);
            try {
                //便于观察,等待一段时间
                Thread.sleep(consumeTaskSleepTime);
                System.out.println("end------" + threadPoolTaskData);
            } catch (Exception e) {
                e.printStackTrace();
            }
            threadPoolTaskData = null;
        }

        public Object getTask() {
            return this.threadPoolTaskData;
        }
    }

}

2)用Spring推出的线程池工具 ThreadPoolTaskExecutor
  1. 配置一个线程池类,把线程池对象ThreadPoolTaskExecutor当作bean交给IOC容器,线程池叫taskExecutor

    /**
     * 线程池配置
     *
     * @author zhangna
     */
    @Configuration
    @EnableAsync    //开启对异步任务的支持
    public class ThreadPoolConfig {
    
        /**
         * 配置了一个线程池,通过spring给我们提供的ThreadPoolTaskExecutor就可以使用线程池。
         * 即把ThreadPoolTaskExecutor当作bean交给IOC管理,然后要使用线程池的时候,从IOC那拿ThreadPoolTaskExecutor即可使用线程池
         *
         * @return
         */
        @Bean("taskExecutor")
        public ThreadPoolTaskExecutor getAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            //设置核心线程数
            executor.setCorePoolSize(5);
            //设置最大线程数
            executor.setMaxPoolSize(20);
            //配置队列大小
            executor.setQueueCapacity(Integer.MAX_VALUE);
            //设置线程活跃时间(秒)
            executor.setKeepAliveSeconds(60);
            //设置默认线程名前缀
            executor.setThreadNamePrefix("blog_threadPool");
            //等待所有任务结束后再关闭线程池
            executor.setWaitForTasksToCompleteOnShutdown(true);
            //执行初始化
            executor.initialize();
            return executor;
        }
    }
    
  2. 定义一个执行异步任务的方法,并给该任务指定要执行它的线程池taskExecutor

    @Component
    public class ThreadService {
    
        /**
         * 异步操作:更新阅读次数
         * @Async 定义此方法为一个异步任务,使用自定义的线程池,即由@EnableAsync所标注的类下定义的线程池
         */
        @Async("taskExecutor")
        public void updateArticleViewCount(ArticleMapper articleMapper, Article article) {
    
            int viewCount = article.getViewCounts();
            //进行articleMapper.update()时,有的值都要设置,所以为了最小限度的更改,就new一个新的Article只设置viewCount
            Article updateArticle = new Article();
            updateArticle.setViewCounts(viewCount+1);
            LambdaUpdateWrapper<Article> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(Article::getId,article.getId());
            //设置一个 为了在多线程的环境下 线程安全
            updateWrapper.eq(Article::getViewCounts,viewCount);
            //update article set view_count=100 where view_count=99 and id=11;
            articleMapper.update(updateArticle,updateWrapper);
    
        }
    }
    
  3. 调用该异步方法

    @Service
    public class ArticleServiceImpl implements ArticleService {
    
        
        @Autowired
        private ThreadService threadService;
        
        ……
            
        threadService.updateArticleViewCount(articleMapper, article);
        
        ……
    }
    
  • 为何Spring要自己写一个ThreadPoolTaskExecutor并推荐代替直接使用ThreadPoolExecutor呢?

    其实最主要的原因很直观:ThreadPoolExecutor是一个不受Spring管理生命周期、参数装配的Java类,而有了ThreadPoolTaskExecutor的封装,线程池才有Spring“内味”

  • @Async注解失效原因及解决方案(spring异步回调)

4. 获取多线程的运行结果—使用CompletableFuture

1)CompletableFuture概述
  1. CompletableFuture是对Feature的增强,Feature只能处理简单的异步任务,而CompletableFuture可以将多个异步任务的结果进行复杂的组合

  2. 当异步方法有返回值时,如何获取异步方法执行的返回结果呢?

这时需要异步调用的方法带有返回值CompletableFuture

2)CompletableFuture使用示例如下
  1. 定义线程池

  2. 定义异步方法

    @Async("taskExecutor")
    public CompletableFuture<String> doSomethingComp(String message) throws InterruptedException {
        log.info("do something1: {}", message);
        Thread.sleep(1000);
        return CompletableFuture.completedFuture("do something1: " + message);
    }
    
    
  3. 调用该异步方法,并对方法的返回值做一些操作,eg:可以3个方法并发调用,然后对结果进行合并处理,阻塞主线程;s1执行完成后并发执行s2和s3,然后消费相关结果,不阻塞主线程等等。可以对不同任务之间的结果做很多有用的处理

    @SneakyThrows
    @GetMapping("/open/somethingComp")
    public String somethingComp() {
        int count = 10;
        CompletableFuture[] futures = new CompletableFuture[count];
        // 开启待返回值得异步方法
        for (int i = 0; i < count; i++) {
            futures[i] = asyncService.doSomethingComp("index = " + i);
        }
        try {// 等待所有任务都执行完
            CompletableFuture.allOf(futures).join();
        } catch (Exception e) {
            System.out.println("CompletableFuture error");
        }
    
        System.out.println("Get all return value! ");
        return "success";
    }
    
3)CompletableFuture进阶使用(结合lambda表达式)

过几天填坑

四、java多线程面试题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值