java并发和线程池简介

来源:互联网 发布:网络劫持什么意思 编辑:程序博客网 时间:2024/06/07 02:35

并发简介

什么时候需要并发

对于单核cpu运行的时候,如果前面的任务线程阻塞了,或者在响应某些事件时,为了保证其他事件任务也可以正常执行,cpu将任务时间进行切片,cpu一般有自己的调度,在不同时间调用不同任务,这个时候就需要并发。
看起来cpu像是能够同时执行多个任务,实际上是在不同任务上来回切换执行。(可能有些人要问切换难道不消耗时间吗),一般来说时间非常短暂,但是当前cpu只有一个任务执行的话,那么就不需要多此一举

实现并发最直接的方式是在操作系统级别使用进程
并发线程,并不是创建的越多越好,一般有自己的负载极限与额外开支,可参考spring-webflux

ErLang 并发语言->适合游戏开发
java的线程机制是抢占式的,优先级高的被调度的几率更高,而不是代表他一定先执行

定义任务

在java中我们描述任务一般用于实现Runnable接口。并且编写run()方法(注意main方法也是一种线程)
第二种我们继承Thread类,然后将我们实现Runnable对象传入Thread的构造器,像这样:new Thread(new Runnable(的实现类)).start();
在使用Thread的时候,由于他要申请一个线程并且完成自己的注册,那么他将会只需获得一个引用,我们知道垃圾回收机制不会回收当前已有引用的对象,那么在run方法执行完成前他都不会被回收执行完了也不一定会被立即回收,要等待CPU对应的回收调度。

Executor类

JUC中提供了Executor执行器间接的帮助我们管理Thread对象
使用Executors的静态方法方法创建ExecutorService(继承自Executor,它是一种具有声明周期的Executor),可以调用他的execute方法:execute(Runnable runnable)

创建ExecutorService的几种常见方法:
/** * 学习之前先看看,Executors中的创建线程池所调用的公共方法 * @param corePoolSize 设置线程池中保存的线程数,不管是不是闲置状态 * @param maximumPoolSize 线程池中最大线程数 * @param keepAliveTime 当线程数过高时,闲置线程在终结前等待新任务的的最大时间 * @param unit 单位时间 * @param workQueue 在任务执行前的工作队列 */public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue) {        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,             Executors.defaultThreadFactory(), defaultHandler);    }
  • Executors.newCachedThreadPool()–一般作为我们的首选方式
public static ExecutorService newCachedThreadPool() {        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                      60L, TimeUnit.SECONDS,                                      new SynchronousQueue<Runnable>());    }    //SynchronousQueue使用非公平工作队列
创建一个用于产生新Thread的线程池,可以被重用只要当前仍是有效的,可以明显的提高工程中执行很多短时同步任务,当线程池中有失效的线程时候,他就会创建一个新的线程来弥补之前的线程。</br>

+ Executors.newFixedThreadPool(int number)–当可以掌握自己需要多少的时候

 public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, nThreads,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>());    }    //LinkedBlockingQueue 感觉字面是链式阻塞队列,遵循FIFO
可以一次传入number的值,那么就会对应数量的线程,可以有效的限制线程的数量。

+ Executors.newScheduledThreadPool(int number)–可以延迟调度
创建一个线程池,可以在给定的时间后延迟运行,或者周期性地执行。(不是很熟悉感觉可以运用于batch之类的)
+ Executors.newWorkStealingPool()–减少队列中的竞争

public static ExecutorService newWorkStealingPool(int parallelism) {        return new ForkJoinPool //这里调用的ForkJoinPool,这里不做详细解释            (parallelism,             ForkJoinPool.defaultForkJoinWorkerThreadFactory,             null, true);    }

创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争.

感兴趣的话click:https://stackoverflow.com/questions/41337451/detailed-difference-between-java8-forkjoinpool-and-executors-newworkstealingpool
下面是我自己的渣翻译:
传统的线程池会保有一个队列,并且会锁着这个队列,当有一个任务出列时就会解锁这个队列,如果有很多任务时间短的任务,那么就会在这个队列里面不断竞争,可以使用一个无锁来解决,但是无法从根底解决问题 。
现在的线程池使用工作窃取-每一个线程拥有自己的队列,当线程池执行一个任务的时候,他只会进入属于他自己的队列中,当线程池想要出列一个任务的时候,他先将自己队列中去寻找一个,如果队列中没有任务,那么他回去别的线程队列中偷取,这样可以降低线程池中的竞争从而提高效率。
newWorkStealingPool会去创建一个拥有许多线程的工作窃取可利用的线程池。
但是同时也暴露出了一个问题,如果我有4核cpu,那么总共就会创建4个线程池,如果其中一个任务阻塞了,比如:一个同步IO流,我没有办法完全利用我的CPUS,因为我所想要的是在任何时刻都是活跃的4个线程,比如,4个正在AES加密中,另外140个正在等待IO完成。
这个是由ForkJoinPool所提供的,如果你的任务又产生了许多子任务并且需要等待子任务完成的话,线程池为了防CPU饱和会重新注入一些新的有效线程。值得一提的是,ForkJoinPool就是使用这种工作窃取。
在说了这么多之后,现在的应用已经是在向创建大量可用线程池,异步流,无锁容器来防止阻塞的趋势靠近了,这个通常也给与我们很好的反馈信息了

  • newSingleThreadExecutor可以理解为线程数量为1的FixedThreadPool
   /**从代码中可以看到与Fixed的类似,表面区别在于它的第一个参数是1,但其实他额外有一种并发保   证,如果由多个任务执行,那么下一个任务必须等到上一个任务执行完,所有任务使用相同线程*/   public static ExecutorService newSingleThreadExecutor() {        return new FinalizableDelegatedExecutorService            (new ThreadPoolExecutor(1, 1,                                    0L, TimeUnit.MILLISECONDS,                                    new LinkedBlockingQueue<Runnable>()));    }
原创粉丝点击