位于更新队列中,在Java并发编程中?
提示
请带着这些问题继续后文,会很大程度上帮助你更好的理解相关知识点。@pdaiSimpleThreadPool
程序中我们创建了固定大小为五个工作线程的线程池。然后分配给线程池十个工作,因为线程池大小为五,它将启动五个工作线程先处理五个工作,其他的工作则处于等待状态,一旦有工作完成,空闲下来工作线程就会捡取等待队列里的其他工作进行执行。这里是以上程序的输出。输出表明线程池中至始至终只有五个名为 "pool-1-thread-1" 到 "pool-1-thread-5" 的五个线程,这五个线程不随着工作的完成而消亡,会一直存在,并负责执行分配给线程池的任务,直到线程池消亡。Executors 类提供了使用了 ThreadPoolExecutor 的简单的 ExecutorService 实现,但是 ThreadPoolExecutor 提供的功能远不止于此。我们可以在创建 ThreadPoolExecutor 实例时指定活动线程的数量,我们也可以限制线程池的大小并且创建我们自己的 RejectedExecutionHandler 实现来处理不能适应工作队列的工作。这里是我们自定义的 RejectedExecutionHandler 接口的实现。RejectedExecutionHandlerImpl.javaThreadPoolExecutor 提供了一些方法,我们可以使用这些方法来查询 executor 的当前状态,线程池大小,活动线程数量以及任务数量。因此我是用来一个监控线程在特定的时间间隔内打印 executor 信息。MyMonitorThread.java这里是使用 ThreadPoolExecutor 的线程池实现例子。WorkerPool.java注意在初始化 ThreadPoolExecutor 时,我们保持初始池大小为 2,最大池大小为 4 而工作队列大小为 2。因此如果已经有四个正在执行的任务而此时分配来更多任务的话,工作队列将仅仅保留他们(新任务)中的两个,其他的将会被RejectedExecutionHandlerImpl 处理。上面程序的输出可以证实以上观点。注意 executor 的活动任务、完成任务以及所有完成任务,这些数量上的变化。我们可以调用 shutdown() 方法来结束所有提交的任务并终止线程池。ThreadPoolExecutor使用详解其实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。Execute原理当一个任务提交至线程池之后:线程池首先当前运行的线程数量是否少于corePoolSize。如果是,则创建一个新的工作线程来执行任务。如果都在执行任务,则进入2.判断BlockingQueue是否已经满了,倘若还没有满,则将线程放入BlockingQueue。否则进入3.如果创建一个新的工作线程将使当前运行的线程数量超过maximumPoolSize,则交给RejectedExecutionHandler来处理任务。当ThreadPoolExecutor创建新线程时,通过CAS来更新线程池的状态ctl.参数corePoolSize 线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize, 即使有其他空闲线程能够执行新来的任务, 也会继续创建线程;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。workQueue 用来保存等待被执行的任务的阻塞队列. 在JDK中提供了如下阻塞队列: 具体可以参考JUC 集合: BlockQueue详解ArrayBlockingQueue: 基于数组结构的有界阻塞队列,按FIFO排序任务;LinkedBlockingQueue: 基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue;SynchronousQueue: 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue;PriorityBlockingQueue: 具有优先级的无界阻塞队列;LinkedBlockingQueue比ArrayBlockingQueue在插入删除节点性能方面更优,但是二者在put(), take()任务的时均需要加锁,SynchronousQueue使用无锁算法,根据节点的状态判断执行,而不需要用到锁,其核心是Transfer.transfer().maximumPoolSize 线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;当阻塞队列是无界队列, 则maximumPoolSize则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入workQueue.keepAliveTime 线程空闲时的存活时间,即当线程没有任务执行时,该线程继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用, 超过这个时间的空闲线程将被终止;unit keepAliveTime的单位threadFactory 创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。默认为DefaultThreadFactoryhandler 线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:AbortPolicy: 直接抛出异常,默认策略;CallerRunsPolicy: 用调用者所在的线程来执行任务;DiscardOldestPolicy: 丢弃阻塞队列中靠最前的任务,并执行当前任务;DiscardPolicy: 直接丢弃任务;当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。三种类型newFixedThreadPool线程池的线程数量达corePoolSize后,即使线程池没有可执行任务时,也不会释放线程。FixedThreadPool的工作队列为无界队列LinkedBlockingQueue(队列容量为Integer.MAX_VALUE), 这会导致以下问题:线程池里的线程数量不超过corePoolSize,这导致了maximumPoolSize和keepAliveTime将会是个无用参数由于使用了无界队列, 所以FixedThreadPool永远不会拒绝, 即饱和策略失效newSingleThreadExecutor初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行.由于使用了无界队列, 所以SingleThreadPool永远不会拒绝, 即饱和策略失效newCachedThreadPool线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列; 和newFixedThreadPool创建的线程池不同,newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销; 执行过程与前两种稍微不同:主线程调用SynchronousQueue的offer()方法放入task, 倘若此时线程池中有空闲的线程尝试读取 SynchronousQueue的task, 即调用了SynchronousQueue的poll(), 那么主线程将该task交给空闲线程. 否则执行(2)当线程池为空或者没有空闲的线程, 则创建新的线程执行任务.执行完任务的线程倘若在60s内仍空闲, 则会被终止. 因此长时间空闲的CachedThreadPool不会持有任何线程资源.关闭线程池遍历线程池中的所有线程,然后逐个调用线程的interrupt方法来中断线程.关闭方式 - shutdown将线程池里的线程状态设置成SHUTDOWN状态, 然后中断所有没有正在执行任务的线程.关闭方式 - shutdownNow将线程池里的线程状态设置成STOP状态, 然后停止所有正在执行或暂停任务的线程. 只要调用这两个关闭方法中的任意一个, isShutDown() 返回true. 当所有任务都成功关闭了, isTerminated()返回true.ThreadPoolExecutor源码详解几个关键属性内部状态其中AtomicInteger变量ctl的功能非常强大: 利用低29位表示线程池中线程数,通过高3位表示线程池的运行状态:RUNNING: -1 << COUNT_BITS,即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;SHUTDOWN: 0 << COUNT_BITS,即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;STOP : 1 << COUNT_BITS,即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;TIDYING : 2 << COUNT_BITS,即高3位为010, 所有的任务都已经终止;TERMINATED: 3 << COUNT_BITS,即高3位为011, terminated()方法已经执行完成任务的执行execute –> addWorker –>runworker (getTask)线程池的工作线程通过Woker类实现,在ReentrantLock锁的保证下,把Woker实例插入到HashSet后,并启动Woker中的线程。 从Woker类的构造方法实现可以发现: 线程工厂在创建线程thread时,将Woker实例本身this作为参数传入,当执行start方法启动线程thread时,本质是执行了Worker的runWorker方法。 firstTask执行完成之后,通过getTask方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask方法会被阻塞并挂起,不会占用cpu资源;execute()方法ThreadPoolExecutor.execute(task)实现了Executor.execute(task)为什么需要double check线程池的状态?在多线程环境下,线程池的状态时刻在变化,而ctl.get()是非原子操作,很有可能刚获取了线程池状态后线程池状态就改变了。判断是否将command加入workque是线程池之前的状态。倘若没有double check,万一线程池处于非running状态(在多线程环境下很有可能发生),那么command永远不会执行。addWorker方法从方法execute的实现可以看出: addWorker主要负责创建新的线程并执行任务 线程池创建新线程执行任务时,需要 获取全局锁:Worker类的runworker方法继承了AQS类,可以方便的实现工作线程的中止操作;实现了Runnable接口,可以将自身作为一个任务在工作线程中执行;当前提交的任务firstTask作为参数传入Worker的构造方法;一些属性还有构造方法:runWorker方法是线程池的核心:线程启动之后,通过unlock方法释放锁,设置AQS的state为0,表示运行可中断;Worker执行firstTask或从workQueue中获取任务:进行加锁操作,保证thread不被其他线程中断(除非线程池被中断)检查线程池状态,倘若线程池处于中断状态,当前线程将中断。执行beforeExecute执行任务的run方法执行afterExecute方法解锁操作通过getTask方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask方法会被阻塞并挂起,不会占用cpu资源;getTask方法下面来看一下getTask()方法,这里面涉及到keepAliveTime的使用,从这个方法我们可以看出线程池是怎么让超过corePoolSize的那部分worker销毁的。注意这里一段代码是keepAliveTime起作用的关键:
allowCoreThreadTimeOut为false,线程即使空闲也不会被销毁;倘若为ture,在keepAliveTime内仍空闲则会被销毁。如果线程允许空闲等待而不被销毁timed == false,workQueue.take任务: 如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,take方法返回任务,并执行;如果线程不允许无休止空闲timed == true, workQueue.poll任务: 如果在keepAliveTime时间内,阻塞队列还是没有任务,则返回null;任务的提交submit任务,等待线程池execute执行FutureTask类的get方法时,会把主线程封装成WaitNode节点并保存在waiters链表中, 并阻塞等待运行结果;FutureTask任务执行完成后,通过UNSAFE设置waiters相应的waitNode为null,并通过LockSupport类unpark方法唤醒主线程;在实际业务场景中,Future和Callable基本是成对出现的,Callable负责产生结果,Future负责获取结果。Callable接口类似于Runnable,只是Runnable没有返回值。Callable任务除了返回正常结果之外,如果发生异常,该异常也会被返回,即Future可以拿到异步执行任务各种结果;Future.get方法会导致主线程阻塞,直到Callable任务执行完成;submit方法AbstractExecutorService.submit()实现了ExecutorService.submit() 可以获取执行完的返回值, 而ThreadPoolExecutor 是AbstractExecutorService.submit()的子类,所以submit方法也是ThreadPoolExecutor`的方法。通过submit方法提交的Callable任务会被封装成了一个FutureTask对象。通过Executor.execute方法提交FutureTask到线程池中等待被执行,最终执行的是FutureTask的run方法;FutureTask对象public class FutureTask<V> implements RunnableFuture<V> 可以将FutureTask提交至线程池中等待被执行(通过FutureTask的run方法来执行)内部状态内部状态的修改通过sun.misc.Unsafe修改get方法内部通过awaitDone方法对主线程进行阻塞,具体实现如下:
如果主线程被中断,则抛出中断异常;判断FutureTask当前的state,如果大于COMPLETING,说明任务已经执行完成,则直接返回;如果当前state等于COMPLETING,说明任务已经执行完,这时主线程只需通过yield方法让出cpu资源,等待state变成NORMAL;通过WaitNode类封装当前线程,并通过UNSAFE添加到waiters链表;最终通过LockSupport的park或parkNanos挂起线程;run方法FutureTask.run方法是在线程池中被执行的,而非主线程通过执行Callable任务的call方法;如果call执行成功,则通过set方法保存结果;如果call执行有异常,则通过setException保存异常;任务的关闭shutdown方法会将线程池的状态设置为SHUTDOWN,线程池进入这个状态后,就拒绝再接受任务,然后会将剩余的任务全部执行完shutdownNow做的比较绝,它先将线程池状态设置为STOP,然后拒绝所有提交的任务。最后中断左右正在运行中的worker,然后清空任务队列。
更深入理解为什么线程池不允许使用Executors去创建? 推荐方式是什么?线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。推荐方式 1首先引入:commons-lang3包推荐方式 2首先引入:com.google.guava包推荐方式 3spring配置线程池方式:自定义线程工厂bean需要实现ThreadFactory,可参考该接口的其它默认实现类,使用方式直接注入bean调用execute(Runnable task)方法即可配置线程池需要考虑因素从任务的优先级,任务的执行时间长短,任务的性质(CPU密集/ IO密集),任务的依赖关系这四个角度来分析。并且近可能地使用有界的工作队列。性质不同的任务可用使用不同规模的线程池分开处理:CPU密集型: 尽可能少的线程,Ncpu+1IO密集型: 尽可能多的线程, Ncpu*2,比如数据库连接池混合型: CPU密集型的任务与IO密集型任务的执行时间差别较小,拆分为两个线程池;否则没有必要拆分。监控线程池的状态可以使用ThreadPoolExecutor以下方法:getTaskCount() Returns the approximate total number of tasks that have ever been scheduled for execution.getCompletedTaskCount() Returns the approximate total number of tasks that have completed execution. 返回结果少于getTaskCount()。getLargestPoolSize() Returns the largest number of threads that have ever simultaneously been in the pool. 返回结果小于等于maximumPoolSizegetPoolSize() Returns the current number of threads in the pool.getActiveCount() Returns the approximate number of threads that are actively executing tasks.参考文章《Java并发编程艺术》https://www.jianshu.com/p/87bff5cc8d8chttps://blog.csdn.net/programmer_at/article/details/79799267https://blog.csdn.net/u013332124/article/details/79587436https://www.journaldev.com/1069/threadpoolexecutor-java-thread-pool-example-executorservice由于问答代码块插入受限,部分代码未完全展示,若有需要可阅读原文:戳我阅读原文
pbe无法与登录队列进行通讯怎么办?
PBE是League of Legends (LOL)的一个测试服,有时会遇到无法登录的问题,其中可能包括“无法与登陆队列进行通讯”的错误信息。造成这个问题的原因一般是服务器或网络出现问题。以下是一些可能的解决方法:
1. 确认PBE服务器是否正常:可以上网查询LOL PBE服务器的在线情况,发现PBE服务器宕机或无法连接时,只能等待服务器恢复正常。
2. 重启客户端并更新:关闭PBE客户端,重新打开并更新PBE客户端,确保使用的是最新版本。如果客户端已经是最新版本,则可以尝试卸载并重新安装PBE客户端。
3. 检查网络连接:检查电脑和网络连接是否正常工作。有时候,可能需要更改网络设置、重置路由器、清空DNS缓存等来修复网络故障。
4. 清除客户端缓存:登录界面下方有一个"修复"按钮,试试点击它来清除客户端缓存。
5. 更改登录区域:有时候,更改登录区域(例如从欧服切换到美服或者自由切换)可以解决无法登录的问题。
如果以上方法仍无法解决问题,可能需要联系游戏官方客服中心来获取进一步的帮助和支持。
明日之后怎么玩?
在明日之后游戏中小伙伴们肯定都是知道金条是十分重要的,玩家们最缺的也就是金条了,而获取金条最快的途径也只有氪金才能获得,可平民玩家是根本氪不起啊,那怎么办呢?这些全要看小伙伴们自己选择的职业了,你的职业能决定着你可以赚多少金条,选对了职业那你就可以赚翻几十万的金条甚至还不止,如果选错了那只好看着别人赚金条了。
游戏中几乎所有能赚钱的副职业都是在多贝雪山这张地图开始赚钱的,因为玩家们庄园等级到达了7级之后就可以制作出属于自己的专属材料了。我们先举个例如,就拿新的材料红麻皮来说,这是在新地图圣托帕尼采麻得到的,但是小伙伴们注意一下自己所在大区红麻皮这个材料的价格,小编这个区的红麻皮一个竟然卖到了473金条的价格,这也太惊人了,就问问小伙伴们这个价格恐怖吗,根本是买不起啊。接着相比矿工这个职业的专属材料一个竟然只卖60金条!红麻皮这个材料就显得尤为的贵重了,竟然是矿工专属材料的8倍之多,简直就是天差地别啊。矿工这个职业也是已经经历了好几个版本,都是作为3个采集职业中一直垫底的职业,因为在目前游戏的地图中完全是不会出现石头缺少的情况,所以一个资源只要是多了那价格肯定就会下来的,其实这3个采集职业都是差不多的,只是看新地图的资源分布情况,而这个就需要小伙伴们赌一把了。如果运气好的话赌赢了,刚好新地图的材料很适合我们的职业,那我们就可以趁机大赚一笔金条了,就像这回新地图圣托帕尼一样,麻在这张地图是非常的少,所以采麻工也就可以大赚一笔了,所以只要赌对了职业那就是等着发财了。当然如果小伙伴们想稳定的去赚金条也是可以的,小编在这推荐小伙伴们选择枪械工和护甲工,这两个职业在当前都是非常火热的,因为在当前的游戏中这两个职业直接决定着武器的合成以及装备的合成,尤其是枪械工,它职业掌握着钢管以及工程塑料的配方,这可都是造高级枪械所需要用的必须品啊,所以玩家们选了枪械工根本不用怕没人买你的东西。而另外一个护甲工,它掌握着背包上限的制作配方,另外还掌握着缝纫线以及皮革的配方,这也都是制作高级装备的必需品。补充一点很多玩家肯定好奇的,这些材料什么时候价格会变高,那就在争霸赛开始的前1~2天左右,这些材料的价格就会开始上涨,涨幅大概会在20%左右,这些全都是商人故意炒出来的,大家不用害怕,完全放心的去卖就行了,一次争霸赛保证能赚个几万金条不是问题的。记住0氪和微氪玩家一定要选有专属材料的职业,这样才能赚到金条。
updatingsteam怎么解决?
这一般是因为网络的问题导致软件或者游戏更新暂停了。然后就会一直显示更新列队中。
你先看下电脑安装游戏的那个盘内存有没有满。尽量留出足够的安装和下载空间给steam下东西。安装完成后会自动帮你清空下载文件的。
可以对于那些在安装的游戏点击下启动游戏。主动触发游戏继续安装或者继续更新。
如果更新了一会又停了。可以看看网络通不通畅。或者更换一个下载游戏的节点区域。重启下steam客户端
steam游戏下载完了?
可以点击更新队列。
这一般是因为网络的问题导致软件或者游戏更新暂停了。然后就会一直显示更新列队中。先看下电脑安装游戏的那个盘内存有没有满。尽量留出足够的安装和下载空间给steam下东西。安装完成后会自动帮清空下载文件的。
可以对于那些在安装的游戏点击下启动游戏。主动触发游戏继续安装或者继续更新。如果更新了一会又停了。可以看看网络通不通畅。或者更换一个下载游戏的节点区域,重启下steam客户端。