Java多线程之线程池

线程池的一些好处

通过重用线程池中的线程,来减少每个线程创建和销毁的性能开销。

对线程进行一些维护和管理,比如定时开始,周期执行,并发数控制等等。

Executor框架

用于任务执行,接口有一个execute方法,用于提交任务。

public interface Executor {
    void execute(Runnable command);
}

Java的四种线程池

newFixedThreadPool

newFixedThreadPool将创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量。如果在满了的情况下继续提交任务,则会进入队列等待。

public class PoolTest {
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 8; i++) {
            int index = i;
            fixedThreadPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + ": " +index);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}
/*
pool-1-thread-1: 0
pool-1-thread-3: 2
pool-1-thread-2: 1
pool-1-thread-4: 3
pool-1-thread-1: 4
pool-1-thread-4: 5
pool-1-thread-2: 6
pool-1-thread-3: 7
*/

newCachedThreadPool

newCachedThreadPool创建一个可缓存的线程池,线程长度如果大于任务数,可以回收,线程池的大小上限为Integer.MAX_VALUE。

public class PoolTest {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 8; i++) {
            int index = i;
            cachedThreadPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + ": " +index);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}
/*
pool-1-thread-1: 0
pool-1-thread-2: 1
pool-1-thread-3: 2
pool-1-thread-5: 4
pool-1-thread-4: 3
pool-1-thread-6: 5
pool-1-thread-7: 6
pool-1-thread-8: 7
*/

newSingleThreadExecutor

newSingleThreadExecutor是一个单线程的线程池,如果线程异常结束,会创建另一个线程来替代,能确保依照任务在队列中的顺序来串行执行。

public class PoolTest {
    public static void main(String[] args) {
        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 8; i++) {
            int index = i;
            newSingleThreadExecutor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + ": " +index);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}
/*
pool-1-thread-1: 0
pool-1-thread-1: 1
pool-1-thread-1: 2
pool-1-thread-1: 3
pool-1-thread-1: 4
pool-1-thread-1: 5
pool-1-thread-1: 6
pool-1-thread-1: 7
*/

newScheduledThreadPool

newScheduledThreadPool创建一个固定长度的线程池,可以延迟或定时地执行任务。

public class PoolTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
        scheduledThreadPool.schedule(() -> System.out.println(Thread.currentThread().getName() + "延迟2秒执行了"), 2, TimeUnit.SECONDS);
        scheduledThreadPool.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName() + "定时执行了任务"), 3, 3, TimeUnit.SECONDS);
    }
}
/*
pool-1-thread-1延迟2秒执行了
pool-1-thread-2定时执行了任务
pool-1-thread-2定时执行了任务
pool-1-thread-2定时执行了任务
pool-1-thread-2定时执行了任务
pool-1-thread-2定时执行了任务
*/

上面四种常见的线程池里面的实现都用到了ThreadPoolExecutor,下面介绍。

ThreadPoolExecutor

ThreadPoolExecutor的通用构造函数

public ThreadPoolExecutor(
            int corePoolSize, // 线程池基本大小
            int maximumPoolSize,  // 线程池最大大小
            long keepAliveTime,  // 线程保持存活时间
            TimeUnit unit,  // 存活时间的单位
            BlockingQueue<Runnable> workQueue, // 任务队列
            ThreadFactory threadFactory, // 线程工程
            RejectedExecutionHandler handler // 饱和策略
) {//...}

BlockingQueue

有四种常见的队列

  1. ArrayBlockingQueue:基于数组的有界阻塞队列。队列按FIFO原则对元素进行排序。 这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
  2. LinkedBlockingQueue:基于链表的无界阻塞队列。与ArrayBlockingQueue一样采用FIFO原则对元素进行排序。基于链表的队列吞吐量通常要高于基于数组的队列。
  3. SynchronousQueue:同步的阻塞队列。其中每个插入操作必须等待另一个线程的对应移除操作,等待过程一直处于阻塞状态,同理,每一个移除操作必须等到另一个线程的对应插入操作。
  4. PriorityBlockingQueue:基于优先级的无界阻塞队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。

饱和策略

当有界队列被填满后,饱和策略开始发挥作用,ThreadPoolExecutor的饱和策略可以通过调用setRejectedExecutionHandler来修改。

  1. AbortPolicy:默认的饱和策略,该策略将抛出未检查的RejectedExecutionException,调用者可以捕获这个异常。
  2. CallerRunsPolicy:实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退给调用者。
  3. DiscardPolicy:当新提交的任务无法保存到队列中等待时,该策略会抛弃该任务。
  4. DiscardOldestPolicy:该策略会抛弃下一个将被执行的任务,然后尝试提交新的任务。(如果工作队列是一个优先队列,那么该策略将导致抛弃优先级最高的任务,所以不能把该策略和优先队列放在一起使用)

线程池的处理流程

简单的例子

public class PoolTest {
    public static void main(String[] args) {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("pool-%d-thread-%d").build();
        ExecutorService pool = new ThreadPoolExecutor(
                4,
                60,
                0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1024),
                namedThreadFactory,
                new ThreadPoolExecutor.AbortPolicy()
        );
        for (int i = 0; i < 5; i++) {
            final int index = i;
            pool.execute(() -> System.out.println(Thread.currentThread().getName() + " " + index));
        }
        pool.shutdown();
    }
}
/*
pool-1-thread-1 0
pool-1-thread-1 4
pool-1-thread-2 1
pool-1-thread-3 2
pool-1-thread-4 3
*/

参考:Java并发实战,http://ifeve.com/java-threadpool/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值