高并发编程四

时间:2021-11-13 14:48:47
  1. 线程池
    一种最为简单的线程创建和回收的方法类:
new Thread(new Runable() {
        public void run() { //do sth}
    }).start();
在 run() 方法结束后,自动回收改线程。
在简单的应用系统中,这段代码并没有太多问题。但是在真实生产环境中,系统由于真实环境需要,
可能会开启很多线程来支撑其应用。

在实际生产环境中,线程的数量必须得到控制。盲目的大量创建线程对于系统性能是很有伤害的。
  1. JDK 对线程池的支持
    JDK 提供了一套 Executor 框架,有效进行线程控制。
    ThreadPoolExecutor 表示一个线程池。
    Executors 扮演线程池工厂的角色,Executors 可以取得一个拥有特定功能的线程池。

    Executor 框架提供了各种类型的线程池:

public static ExecutorService newFixedThreadPool(int nThreads)
    public static ExecutorService newSingleThreadExecutor()
    public static ExecutorService newCachedThreadPool()
    public static ScheduleExecutorService newSingleThreadScheduledExecutor()
    public static ScheduleExecutorService newScheduledThreadPool(int corePoolSize)
1) newFiexedThreadPool() :返回一个固定线程数的线程池。线程池中线程数量始终不变。
当新任务提交时,线程池有空闲线程,就立即执行。若没有,会被存在一个任务队列中,有空闲时,便处理任务队中的任务。
2) newSingleTHreadExecutor() :返回一个只有一个线程的线程池。
若多余一个任务被提交,任务会被保存在一个任务队中,待线程空闲,按先入先出的顺序执行队列中的任务。
3) newCachedThreadPool() :返回一个可根据实际情况调整线程数量的线程池。
线程池数量不确定,若有空闲线程可以复用,则优先使用。若所有线程均在工作,又有新任务提交,则会创建新的线程处理。所有线程在当前任务执行完毕后,将返回线程池进行复用。
4) newSingleThreadScheduledExecutor() 返回一个 ScheduledExecutorService 对象,线程池大小为1.
ScheduledExecutorService 接口在 ExecutorService 接口之上扩展了在给定时间执行某任务的功能。
如在某个固定的延时之后执行,或者周期性执行某个任务。
5) new ScheduledThreadPool() :返回一个 ScheduledExecutorService 对象。
但改线程池可以指定线程数量。

//固定线程池
public class ThreadPoolDemo {
        public static class MyTask implements Runnable {

            @Override
            public void run() {
                System.out.println(System.currentTimeMillis() + ":Thread ID:" + Thread.currentThread().getId());

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        public static void main(String[] args) {
            MyTask task = new MyTask();

            ExecutorService es = Executors.newFixedThreadPool(5);
            for (int i = 0;i < 10;i++) {
                es.submit(task);
            }
        }
    }
输出:
1483410660734:Thread ID:12
1483410660734:Thread ID:15
1483410660734:Thread ID:14
1483410660734:Thread ID:13
1483410660734:Thread ID:11
1483410661737:Thread ID:12
1483410661737:Thread ID:15
1483410661737:Thread ID:14
1483410661737:Thread ID:11
1483410661737:Thread ID:13
  1. 计划任务
    ScheduledExectorServic 对象,主要方法:
public ScheduledFuture<?> schedule(Runable command,long delay,TimeUnit unit)
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,
    long period,TimeUnit unit)
    public ScheduleFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,
    long delay TimeUnit unit)
scheduleAtFixedRate() 和 scheduleWithFixedDelay() 会对任务进行周期性调度。
对于 fixedRate, 任务调度的频率是一定的。它是以上一个任务开始执行时间为起点,
之后的 period 时间,调度下一次任务。
FixDelay,上一个任务结束之后,再经过 delay时间进行任务调度。
  1. 扩展线程池
    ThreadPoolExecutor 提供了 beforeExecute()、afterExecute() 和 terminated() 三个接口扩展

  2. 优化线程池线程数量
    < java concurrency in practice> 一书中给出估算线程池大小的经验公式:
    Ncpu = CUP 的数量
    Ucpu = 目标CPU的使用率,0 <= Ucpu <= 1
    W / C = 等待时间与计算时间的比率

    为保持处理器达到期望的使用率,最优的池的大小等于:

    Nthreads = Ncpu * Ucpu * (1 + W/C )
    

    在 java 中,可以通过:

    Runtime.getRuntime().availableProcessors()
    

    取得可用的CPU数量。

  3. 在线程池中寻找堆栈
    一种最简单的方法,放弃 submit(),改用 execute().
    pools.execute(new DivTask(100,i));

    或者使用下面改造:
    Future re = pools.submit(new DivTask(100,i));
    re.get();
    
  4. 并发集合简介
    1) concurrentHashMap: 高效的并发 HashMap. 线程安全的 HashMap.
    2) CopyOnWriteArrayList: 是一个 List,在读多写少的场合,这个 list 的性能非常好,远远好于 Vector
    3) concurrentLinkedQueue: 高效的并发队列,使用链表实现。线程安全的 LinkedList。
    4) BlockingQueue: 接口,JDK内部通过链表、数组等方式实现这个接口。
    表示阻塞队列,非常适合用于作为数据共享的通道。
    5) concurrentSkipListMap: 跳表的实现。是一个Map,使用跳表的数据结构进行快速查找。

    java.util 下的 Vector 是线程安全的,Collections 可以帮助我们将任意集合包装成线程安全的集合。