当前位置: 首页 > 科技观察

高并发一览,线程与线程池总结

时间:2023-03-22 15:56:02 科技观察

1JAVA线程的实现原理Java线程是基于操作系统(非用户态)的原生线程模型,通过系统调用,程序交给系统去调度执行java线程有自己的虚拟机栈。当JVM准备好堆栈、程序计数器和工作内存时,它会分配一个系统本机线程来执行。当Java线程结束时,本机线程被回收。原生线程初始化完成后,会调用Java线程的run方法。当JAVA线程结束时,native线程和Java线程的所有资源都会被释放。java方法的执行对应虚拟机栈的一个栈帧,用来存放局部变量、操作数栈、动态链接、方法出口等。2、JAVA线程的生命周期CycleNew(新建状态):使用new关键字创建线程后,线程处于new状态。此时只有JVM为其分配内存并初始化其成员变量Runnable(就绪状态):当Thread.start方法被调用时,线程处于就绪状态。JVM会为其分配一个虚拟机栈等,然后等待系统调度运行(运行状态):处于就绪状态的线程获得CPU,执行run方法时,线程处于运行状态Terminated(线程死亡):线程正常结束,或者抛出未捕获的Throwable并调用Thread.stop结束线程会导致线程死亡,等待系统调度执行publicsynchronizedvoidstart(){//synchronized同步执行if(threadStatus!=0)//0代表新的状态,不为0会抛出错误thrownewIllegalThreadStateException();...start0();//本地方法方法privatenativevoidstart0()...}//运行状态,新线程执行的代码方法,可由子类重写publicvoidrun(){if(target!=null){//target为Runnable,newThread(Runnable)传入时target.run();}}"线程终止函数"//Thread.java@Deprecatedpublicfinalvoidstop();//中断线程publicvoidinterrupt()//判断当前线程是否处于中断状态publicstaticbooleaninterrupted()使用stop会强行终止线程,线程持有的所有锁突然释放(不可控),同步的逻辑由锁被摧毁。不推荐使用中断函数来打断线程,但并不一定能让线程退出。它比stop函数更优雅,可以控制当线程处于调用sleep和wait的阻塞状态时,会抛出InterruptedException,代码会在内部捕获,然后结束线程。如果线程处于非阻塞状态,程序需要调用interrupted()判断然后决定是否退出其他常用方法//Thread.java//阻塞等待其他线程publicfinalsynchronizedvoidjoin(finallongmillis)//暂时让CPU执行publicstaticnativevoidyield();//休眠一段时间publicstaticnativevoidsleep(longmillis)throwsInterruptedException;start和run方法的区别在于Thread类的方法,从线程的生命周期来看,start的执行并不代表新线程的执行,而是让JVM分配虚拟机栈并进入Runnable状态。start的执行仍在旧线程上运行。线程由系统调度。获取CPU时,执行方式必须是继承Thread或者实现Runnable接口。Thread.sleep和Object.wait的区别在于Thread.sleep需要指定一个休眠时间,时间到了会继续运行;它与锁机制无关,不能加锁后不需要释放锁。synchronized中需要调用Object.wait,否则会报IllegalMonitorStateException错误。wait方法会释放锁,同样需要调用锁对象Object.notify来唤醒线程。4线程池及其优点每次使用都要创建一个线程,用完再销毁,开销很大。如果使用缓存策略(线程池),将之前创建的线程暂存起来,并复用这些线程,可以减少程序消耗,提高线程利用率,减少资源消耗:复用线程可以减少线程创建和运行带来的消耗增加destruction响应速度:当任务到达时,可以立即执行,无需等待创建线程提高线程可管理性:使用线程池进行统一分配、监控和调优5个JDK封装的线程池//ThreadPoolExecutor.javapublicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler)1corePoolSize:核心线程数,线程池维护的线程数2maximumPoolSize:最大线程数,当阻塞队列不能再接受任务和maximumPoolSize大于corePoolSize创建一个非核心线程来执行。当没有任务执行时,它将被销毁。3keepAliveTime:非核心线程在空闲时间的存活时间。4TimeUnit:与keepAliveTime配合使用,表示keepAliveTime参数的时间单位。Queue6threadFactory:线程创建工厂7handler:拒绝策略,线程数达到maximumPoolSize,使用拒绝策略处理任务提交6线程池原理的执行过程//ThreadPoolExecutor.javapublicvoidexecute(Runnablecommand){...if(workerCountOf(c)());}"newCachedThreadPool"核心池大小为0,线程池最大线程数为最大整数,任务提交先加入阻塞队列其中,非核心线程如果60s没有任务执行会被销毁,并且阻塞队列是SynchronousQueue。newCachedThreadPool会不断创建新线程执行任务,不推荐使用//Executors.javapublicstaticExecutorServicenewCachedThreadPool(){returnnewThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,newSynchronousQueue());}「newScheduledThreadPool》ScheduledThreadPoolSTPE)其实是ThreadPoolExecutor的子类,可以指定核心线程数。队列是STPE的内部类DelayedWorkQueue。"STPE的优点是A可以延时执行任务,B可以执行有返回值的任务",threadFactory);}//指定延迟执行时间publicScheduledFutureschedule(Callablecallable,longdelay,TimeUnitunit)"newSingleThreadExecutor"与newFixedThreadPool的构造方法一致,只是线程数为设置为1。SingleThreadExecutor相对于新线程的优势是;"当线程运行过程中抛出异常时,新线程会加入线程池完成下一个任务;阻塞队列可以保证任务按照先进先出执行"//Executors.javapublicstaticExecutorServicenewSingleThreadExecutor(){returnnewFinalizableDelegatedExecutorService(newThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,newLinkedBlockingQueue()));//无界队列}9如果优雅地关闭线程池,必须先关闭池中的线程,上面第三点提到就是暴力强制停止线程会导致同步数据不一致,所以我们需要调用中断关闭线程,线程池提供了两种关闭方法,shutdownNow和shuwdownshutdownNow:线程池拒绝接受新任务,立即关闭线程池(正在执行的会继续执行),排队的任务不再执行,未执行的任务返回ListpublicListshutdownNow(){...finalReentrantLockmainLock=this.mainLock;mainLock.lock();//锁try{checkShutdownAccess();advanceRunState(STOP);interruptWorkers();//中断关闭线程tasks=drainQueue();//unexecutedtasks...shuwdown:threadpoolrefusestoreceivenewtaskswhilewaitingtasksinthethreadpool执行后关闭线程池,代码类似shutdownNow,不再贴10线程池为什么使用ablockingqueue首先考虑为什么线程池中的线程不会被释放,它是如何管理线程的生命周期的//ThreadPoolExecutor.Worker.classfinalvoidrunWorker(Workerw){...//工作线程会进入一个循环获取任务执行逻辑while(task!=null||(task=getTask())!=null)...}privateRunnablegetTask(){...Runnabler=timed?workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS):workQueue.take();//线程会阻塞并挂起等待任务,...}可以看出,当没有任务执行,线程池实际上是使用阻塞队列的take方法挂起,从而维持核心线程的存活11线程池中的worker继承了AQS的意思。中断setState(-1);//禁止中断suntilrunWorkerthis.firstTask=firstTask;this.thread=getThreadFactory().newThread(this);}finalvoidrunWorker(Workerw){....//对应构造Worker为setState(-1)w.unlock();//allowinterruptsbooleancompletedAbruptly=true;....w.lock();//锁同步....try{...task.run();afterExecute(task,null);}finally{....w.unlock();//释放锁}worker继承了AQS的意思:A禁止线程在启动前被打断;B同步runWorker方法的处理逻辑12拒绝策略AbortPolicyRejectedExecutionException"DiscardOldestPolicy"丢弃队列最前面的任务,然后重新提交被拒绝的任务"DiscardPolicy"丢弃任务,但不抛出异常"CallerRunsPolicy?A被拒绝任务的处理程序直接在{@codeexecute}方法的调用线程中运行被拒绝的任务,除非执行程序已关闭n,在这种情况下任务被丢弃。?如果任务被拒绝,任务将由“提交任务的线程”执行13ForkJoinPool理解一波ForkJoinPool不同于ThreadPoolExecutor,适用于执行可以分解子任务的任务,比如树在一些递归的场景比如遍历和归并排序,ForkJoinPool的每个线程都有对应的双端队列deque;当线程中的任务被fork拆分后,拆分后的子任务会被放入线程自己的deque中,减少线程间的竞争。窃取工作窃取算法当一个线程执行完自己的双端队列任务,而其他线程双端队列有更多任务时,将启动窃取策略,从其他线程双端队列队列的尾部获取线程使用RecursiveTask实现forkjoin过程demopublicclassForkJoinPoolTest{publicstaticvoidmain(String[]args)throwsExecutionException,InterruptedException{ForkJoinPoolforkJoinPool=newForkJoinPool();for(inti=0;i<10;i++){ForkJoinTasktask=forkJoinPool.submit(newFibonacci(i));System.out.println(task.get());}}staticclassFibonacciextendsRecursiveTask{intn;publicFibonacci(intn){this.n=n;}@OverrideprotectedIntegercompute(){if(n<=1){returnn;}Fibonaccifib1=newFibonacci(n-1);fib1.fork();//相当于新开一个线程执行fibonaccifib2=newFibonacci(n-2);fib2.fork();//相当于新开一个线程执行returnfib1.join()+fib2.join();//合并阻塞返回结果}}}作者:clswcl链接:https://juejin.im/post/5f016bd16fb9a07e9a07a09f来源:掘金版权归作者所有。如需商业转载,请联系作者获得授权。非商业转载请注明出处。