Java多线程获取网页数据并更新到数据库

首先创建线程池MyThreadPool:

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/*
* 多线程线程池配置
*/
public class MyThreadPool {

    public static ThreadPoolExecutor threadExecutor = new ThreadPoolExecutor(
            10,   //核心线程数
            Runtime.getRuntime().availableProcessors(),
            100,  //线程空闲时的存活时间
            TimeUnit.SECONDS,  //时间单位设置-秒
            new LinkedBlockingDeque(1024), //线程阻塞队列
            Executors.defaultThreadFactory(),  //创建线程的工厂
            new ThreadPoolExecutor.CallerRunsPolicy() //线程池的饱和策略
    );
}

解释一下这几个参数:
1. corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;
如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。
2. maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize,这里使用的Runtime.getRuntime().availableProcessors(),作用是获取可用的计算资源
3. keepAliveTime
线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。默认情况下,该参数只在线程数大于corePoolSize时才有用
4.TimeUnit
keepAliveTime的时间单位,可选天时分秒毫秒等
5.workQueue
workQueue必须是BlockingQueue阻塞队列。当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能

队列类别 结构
ArrayBlockingQueue 数组结构组成有界阻塞队列,先进先出原则,初始化必须传大小,take和put时候用的同一把锁
LinkedBlockingQueue 链表结构组成的有界阻塞队列,先进先出原则,初始化可以不传大小,put,take锁分离
PriorityBlockingQueue 支持优先级排序的无界阻塞队列,排序,自然顺序升序排列,更改顺序:类自己实现compareTo()方法,初始化PriorityBlockingQueue指定一个比较器Comparator
DelayQueue 使用了优先级队列的无界阻塞队列,支持延时获取,队列里的元素要实现Delay接口。DelayQueue非常有用,可以将DelayQueue运用在以下应用场景。缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。还有订单到期,限时支付等等。
SynchronousQueue 不存储元素的阻塞队列,每个put操作必须要等take操作
LinkedTransferQueue 链表结构组成的无界阻塞队列,Transfer,tryTransfer,生产者put时,当前有消费者take,生产者直接把元素传给消费者
LinkedBlockingDeque 链表结构组成的双向阻塞队列,可以在队列的两端插入和移除,xxxFirst头部操作,xxxLast尾部操作。工作窃取模式

这里使用最后一种,阻塞队列大小可以适当调大一点

6.ThreadFactory
创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名Executors静态工厂里默认的threadFactory,线程的命名规则是“pool-数字-thread-数字”,这里使用默认

7.RejectedExecutionHandler
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:

(1)AbortPolicy:直接抛出异常,默认策略;
(2)CallerRunsPolicy:用调用者所在的线程来执行任务;
(3)DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
(4)DiscardPolicy:直接丢弃任务;
这里需要注意的是,一般网上默认的例子都是使用第一个,这里我在使用的时候,由于参数设置的不合理,导致直接抛出异常,终止了程序,这里我是用的是第二种。

业务中使用的逻辑如下:

public void updateByBatch(List<VervelClient> clients) {
        int tatol = clients.size();
        //开启多线程处理
        CountDownLatch countDownLatch = new CountDownLatch(tatol);
        clients.forEach(vervelClient -> {
            MyThreadPool.threadExecutor.execute(()->{
                updateSelectiveById(vervelClient);
                countDownLatch.countDown();
            });
        });
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

countDown()
如果当前计数器的值大于零,则将其减一,如果新的计数器值等于零,则释放所有等待的线程。
计数器变为零后就不会再减了。
如果当前计数器为零,则什么都不做。
此方法不会阻塞。
await()
导致当前线程等待,直到计数器的值变为零,除非线程被中断。如果计数器已经为零了,则立即返回。

不使用多线程,一次次的更新数据库非常慢,使用多线程,速度得到极大提升

参考链接:记录一次多线程处理mysql大批量数据更新

ThreadPoolExecutor 配置详解

[Java] 如何理解和设置ThreadPoolExecutor三大核心属性?什么情况下工作线程数会突破核心线程数?任务拒绝策略都有哪些?

你可能感兴趣的:(IT,work,java,数据库,开发语言)