学做面食最好的网站,移动应用开发技术有哪些,电子工程专辑网站,做简易动画的网站一、线程池 1.1、什么是线程池 线程池是一种多线程的处理方式#xff0c;利用已有线程对象继续服务新的任务#xff08;按照一定的执行策略#xff09;#xff0c;而不是频繁地创建销毁线程对象#xff0c;由此提高服务的吞吐能力#xff0c;减少CPU的闲置时间。具体组成…一、线程池 1.1、什么是线程池 线程池是一种多线程的处理方式利用已有线程对象继续服务新的任务按照一定的执行策略而不是频繁地创建销毁线程对象由此提高服务的吞吐能力减少CPU的闲置时间。具体组成部分包括 1、线程池管理器ThreadPool用于创建和管理线程池包括创建线程池、销毁线程池添加新任务。 2、工作线程Worker线程池中的线程闲置的时候处于等待状态可以循环回收利用。 3、任务接口Task每个任务必须实现的接口类为工作线程提供调用主要规定了任务的入口、任务完成的收尾工作、任务的状态。 4、等待队列Queue存放等待处理的任务提供缓冲机制。
1.2、线程池的种类
1、FixedThreadPool固定数量的线程池线程池中的线程数量是固定的不会改变。 2、SingleThreadExecutor单一线程池线程池中只有一个线程。 3、CachedThreadPool缓存线程池线程池中的线程数量不固定会根据需求的大小进行改变。 4、ScheduledThreadPool计划任务调度的线程池用于执行计划任务比如每隔5分钟怎么样。
ThreadPoolExecutor构造函数中参数的含义
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueueRunnable workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize 0 ||maximumPoolSize 0 ||maximumPoolSize corePoolSize ||keepAliveTime 0)throw new IllegalArgumentException();if (workQueue null || threadFactory null || handler null)throw new NullPointerException();this.corePoolSize corePoolSize;this.maximumPoolSize maximumPoolSize;this.workQueue workQueue;this.keepAliveTime unit.toNanos(keepAliveTime);this.threadFactory threadFactory;this.handler handler;}
1、corePoolSize线程池中核心线程数的数目 2、maximumPoolSize线程池中最多能容纳多少个线程 3、keepAliveTime当现在线程数目大于corePoolSize时超过keepAliveTime时间后多出corePoolSize的那些线程将被终结。 4、unitkeepAliveTime的单位 5、workQueue当任务数量很大线程池中线程无法满足时提交的任务会被放到阻塞队列中线程空闲下来则会不断从阻塞队列中取数据。 6、threadFactory执行程序创建新线程时使用的工厂。 7、handler由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
RejectedExecutionHandler拒绝的4种策略 1、AbortPolicy如果不能接受任务了则抛出异常。 2、CallerRunsPolicy如果不能接受任务了则让调用的线程去完成。 3、DiscardOldestPolicy如果不能接受任务了则丢弃最老的一个任务由一个队列来维护。 4、DiscardPolicy如果不能接受任务了则丢弃任务。
1.3、携带结果的任务Callable和Future/FutureTask
1、Callable解决Runnable接口不能返回一个值或受检查的异常可以采用Callable接口实现一个任务。 2、Future表示异步计算的结果可以对于具体的Runnable或者Callable任务进行查询是否完成查询是否取消获取执行结果取消任务等操作。 3、FutureTask是一个RunnableFutureV而RunnableFuture实现了Runnbale又实现了FutrueV这两个接口。
1.4、Fork/Join框架 Fork/Join框架是Java7提供了的一个用于并行执行任务的框架 是一个把大任务分割成若干个小任务最终汇总每个小任务结果后得到大任务结果的框架。 Fork/Join类似MapReduce算法两者区别是Fork/Join 只有在必要时如任务非常大的情况下才分割成一个个小任务而 MapReduce总是在开始执行第一步进行分割。看来Fork/Join更适合一个JVM内线程级别而MapReduce适合分布式系统。
1、工作窃取算法 工作窃取work-stealing算法是指某个线程从其他队列里窃取任务来执行。工作窃取的运行流程图如下 那么为什么需要使用工作窃取算法呢 假如我们需要做一个比较大的任务我们可以把这个任务分割为若干互不依赖的子任务为了减少线程间的竞争于是把这些子任务分别放到不同的队列里并为每个队列创建一个单独的线程来执行队列里的任务线程和队列一一对应比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着不如去帮其他线程干活于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列所以为了减少窃取任务线程和被窃取任务线程之间的竞争通常会使用双端队列被窃取任务线程永远从双端队列的头部拿任务执行而窃取任务的线程永远从双端队列的尾部拿任务执行。 工作窃取算法的优点是充分利用线程进行并行计算并减少了线程间的竞争其缺点是在某些情况下还是存在竞争比如双端队列里只有一个任务时。并且消耗了更多的系统资源比如创建多个线程和多个双端队列。
2、如何设计一个Fork/Join框架 第一步分割任务。首先我们需要有一个fork类来把大任务分割成子任务有可能子任务还是很大所以还需要不停的分割直到分割出的子任务足够小。 第二步执行任务并合并结果。分割的子任务分别放在双端队列里然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里启动一个线程从队列里拿数据然后合并这些数据。 Fork/Join使用两个类来完成以上两件事情 1、ForkJoinTask我们要使用ForkJoin框架必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join()操作的机制通常情况下我们不需要直接继承ForkJoinTask类而只需要继承它的子类Fork/Join框架提供了以下两个子类 RecursiveAction用于没有返回结果的任务。 RecursiveTask 用于有返回结果的任务。 2、ForkJoinPool ForkJoinTask需要通过ForkJoinPool来执行任务分割出的子任务会添加到当前工作线程所维护的双端队列中进入队列的头部。当一个工作线程的队列里暂时没有任务时它会随机从其他工作线程的队列的尾部获取一个任务。
2、Fork/Join框架的实现原理 ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成ForkJoinTask数组负责存放程序提交给ForkJoinPool的任务而ForkJoinWorkerThread数组负责执行这些任务。 ForkJoinTask的fork方法实现原理当我们调用ForkJoinTask的fork方法时程序会调用ForkJoinWorkerThread的pushTask方法异步的执行这个任务然后立即返回结果。pushTask方法把当前任务存放在ForkJoinTask 数组queue里。然后再调用ForkJoinPool的signalWork()方法唤醒或创建一个工作线程来执行任务。 ForkJoinTask的join方法实现原理Join方法的主要作用是阻塞当前线程并等待获取结果。
4、Fork/Join框架的异常处理 ForkJoinTask在执行的时候可能会抛出异常但是我们没办法在主线程里直接捕获异常所以ForkJoinTask提供了isCompletedAbnormally()方法来检查任务是否已经抛出异常或已经被取消了并且可以通过ForkJoinTask的getException方法获取异常。
二、JDK并发包 2.1、同步控制工具类
1、Semaphore 计数信号量常用于限制可以访问某些资源物理或逻辑的线程数目。是一个共享锁允许N个线程同时进入临界区, 但是超出许可范围的只能等待如果N 1, 则类似于lock
2、ReentrantLock 简而言之, 就是自由度更高的synchronized, 主要具备以下优点 可重入单线程可以重复进入但要重复退出 可中断lock.lockInterruptibly() 可限时超时不能获得锁就返回false不会永久等待构成死锁 公平锁先来先得public ReentrantLock(boolean fair)默认锁不公平的, 根据线程优先级竞争
3、Condition 类似于 Object.wait()和Object.notify(), 需要与ReentrantLock结合使用.
4、ReadWriteLock 读写分离锁, 可以大幅提升系统并行度. 读-读不互斥读读之间不阻塞。 读-写互斥读阻塞写写也会阻塞读。 写-写互斥写写阻塞。
5、CountDownLatch倒数计时器 一种典型的场景就是火箭发射。在火箭发射前为了保证万无一失往往还要进行各项设备、仪器的检查。 只有等所有检查完毕后引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程, 等待所有检查线程全部完工后再执行
6、CyclicBarrier循环栅栏 Cyclic意为循环也就是说这个计数器可以反复使用。比如假设我们将计数器设置为10。那么凑齐 第一批10个线程后计数器就会归零然后接着凑齐下一批10个线程
7、LockSupport 一个线程阻塞工具, 可以在任意位置让线程阻塞。与wait/notify比较如果unpark发生在park之前 并不会导致线程冻结也不需要获取锁 LockSupport比Object的wait/notify有两大优势 1、LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了实现了线程间的解耦 2、unpark函数可以先于park调用所以不需要担心线程间的执行的先后顺序
2.2、并发容器
1、Collections.synchronizedMap 其本质是在读写map操作上都加了锁因此不推荐在高并发场景使用
2、ConcurrentHashMap 内部使用分区Segment来表示不同的部分, 每个分区其实就是一个小的hashtable各自有自己的锁只要多个修改发生在不同的分区他们就可以并发的进行。把一个整体分成了16个Segment最高支持16个线程并发修改
3、BlockingQueue 阻塞队列, 主要用于多线程之间共享数据当一个线程读取数据时如果队列是空的则当前线程会进入等待状态如果队列满了当一个线程尝试写入数据时同样会进入等待状态适用于生产消费者模型因为BlockingQueue在put、take等操作有锁因此非高性能容器如果需要高并发支持的队列则可以使用ConcurrentLinkedQueue他内部运用了大量无锁操作
4、CopyOnWriteArrayList CopyOnWriteArrayList通过在新增元素时复制一份新的数组出来并在其中写入数据之后将原数组引用指向到新数组其Add操作是在内部通过ReentrantLock进行锁保护防止多线程场景复制多份数组而Read操作内部无锁直接返回数组引用并发下效率高因此适用于读多写少的场景
参考文章
1、Java多线程并发编程一览笔录