多线程第三阶段课程
2020-11-16 本文已影响0人
jiahzhon
实现线程的方式
-
继承Thread类
-
实现Runnable接口
-
ExecutorService、Callable、Future有返回值的线程。
-
有返回值的任务必须实现Callable接口,类型的,无返回值的任务必须实现Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现有返回结果的多线程。
1
-
-
基于线程池的方式
2 -
CAS算法
- CAS是靠硬件实现的,从而在硬件层面提升效率。
- 实现过程
-
假如现在有两个线程t1,t2,,他们各自的运行环境中都有共享变量的副本V1、V2,预期值E1、E2,预期主存中的值还没有被改变,假设现在在并发环境,并且t1先拿到了执行权限,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试,然后t1比较预期值E1和主存中的V,发现E1=V,说明预期值是正确的,执行N1=V1+1,并将N1的值传入主存。这时候贮存中的V=21,然后t2又紧接着拿到了执行权,比较E2和主存V的值,由于V已经被t1改为21,所以E2!=V,t2线程将主存中已经改变的值更新到自己的副本中,再发起重试;直到预期值等于主存中的值,说明没有别的线程对旧值进行修改,继续执行代码,退出。
20181122105801128.png
-
- 结论
- 其实就是拿副本中的预期值与主存中的值作比较,如果相等就继续替换新值,如果不相等就说明主存中的值已经被别的线程修改,就继续重试。
- CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,所以非常快。而且CAS避免了请求操作系统来裁定锁的问题,不用麻烦操作系统,直接在CPU内部就搞定了。
CountdownLatch和CyclicBarrier不同
- CountDownLatch不能reset,而CyclicBarrier是可以循环使用的。
- CountDownLatch工作线程之间互不关心,而CyclicBarrier必须等到同一个共同的点才去执行某个动作。
Executor
- 线程池四种分类
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- ThreadPoolExecutor线程池的关闭
- shutdownNow:线程池拒接收新提交的任务,同时立马关闭线程池,线程池里的任务不再执行。
- shutdown:线程池拒接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池。
- 优雅地关闭线程池:
- 使用shutdownNow方法,可能会引起报错,使用shutdown方法可能会导致线程关闭不了。所以当我们使用shutdownNow方法关闭线程池时,一定要对任务里进行异常捕获。
- 当我们使用shuwdown方法关闭线程池时,一定要确保任务里不会有永久阻塞等待的逻辑,否则线程池就关闭不了。
- 最后,一定要记得,shutdownNow和shuwdown调用完,线程池并不是立马就关闭了,要想等待线程池关闭,还需调用awaitTermination方法来阻塞等待。
- 拒绝策略
- 当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略。
- CallerRunsPolicy - 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
- AbortPolicy - 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
- DiscardPolicy - 直接丢弃,其他啥都没有。
- DiscardOldestPolicy - 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入。