ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java面试——并发编程(一)

2026/8/24 22:44:56 拓冰建站 浏览量
Java面试——并发编程(一) 并发编程1、Java线程的创建方式1.1.1、继承Thread类1.1.2、实现Runnable接口1.1.3、通过ExecutorService和CallableClass实现有返回值的线程1.1.4、基于线程池2、线程池的工作原理2.1、线程复用2.2、线程池的核心组件和核心类2.3、Java线程池的工作流程2.4、线程池的拒绝策略2.4.1、AbortPolicy2.4.2、CallerRunsPolicy2.4.3、DiscardOldestPolicy2.4.4、DiscardPolicy2.4.5、自定义拒绝策略3、5种常用的线程池3.1、newCachedThreadPool3.2、newFixedThreadPool3.3、newScheduledThreadPool3.4、newSingleThreadExecutor3.5、newWorkStealingPool4、线程的生命周期4.1、新建状态New4.2、就绪状态Runnable4.3、运行状态Running4.4、阻塞状态Blocked4.5、线程死亡Dead5、线程的基本方法5.1、线程等待wait方法5.2、线程睡眠sleep方法5.3、线程让步yield方法5.4、线程中断interrupt方法5.5、线程加入join方法5.6、线程唤醒notify方法5.7、后台守护线程setDaemon方法5.8、sleep方法与wait方法的区别5.9、start方法与run方法的区别5.10、终止线程的4种方式5.10.1、正常运行结束5.10.2、使用退出标志退出线程5.10.3、使用Interrupt方法终止线程5.10.4、使用stop方法终止线程不安全相对于传统的单线程多线程能够在操作系统多核配置的基础上能够更好地利用服务器的多个CPU资源使程序运行起来更加高效。Java通过提供对多线程的支持来在一个进程内并发执行多个线程每个线程都并行执行不同的任务以满足编写高效率程序的要求。1、Java线程的创建方式常见的Java线程的4种创建方式分别为继承Thread类、实现Runnable接口、通过ExecutorService和CallableClass实现有返回值的线程、基于线程池如图所示。1.1.1、继承Thread类Thread类实现了Runnable接口并定义了操作线程的一些方法我们可以通过继承Thread类的方式创建一个线程。具体实现为创建一个类并继承Thread接口然后实例化线程对象并调用start方法启动线程。start方法是一个native方法通过在操作系统上启动一个新线程并最终执行run方法来启动一个线程。run方法内的代码是线程类的具体实现逻辑。具体的实现代码如下//step 1通过继承Thread类创建NewThread线程publicclassNewThreadextendsThread{publicvoidrun(){System.out.println(create a thread by extends Thread);}}//step 2实例化一个NewThread线程对象NewThreadnewThreadnewNewThread();//step 3调用start方法启动NewThread线程newThread.start();以上代码定义了一个名为NewThread的线程类该类继承了Thread, run方法内的代码为线程的具体执行逻辑在使用该线程时只需新建一个该线程的对象并调用其start方法即可。1.1.2、实现Runnable接口基于Java编程语言的规范如果子类已经继承extends了一个类就无法再直接继承Thread类此时可以通过实现Runnable接口创建线程。具体的实现过程为通过实现Runnable接口创建ChildrenClassThread线程实例化名称为childrenThread的线程实例创建Thread类的实例并传入childrenThread线程实例调用线程的start方法启动线程。具体的实现代码如下//step 1通过实现Runnable接口方式创建ChildrenClassThread线程publicclassChildrenClassThreadextendsSuperClassimplementsRunnable{publicvoidrun(){System.out.println(create a thread by implements Runnable );}}//step 2实例化一个ChildrenClassThread对象ChildrenClassThreadchildrenThreadnewChildrenClassThread();//step 3创建一个线程对象并将其传入已经实例化好的childrenThread实例ThreadthreadnewThread(childrenThread);//step 4调用start方法启动一个线程thread.start();事实上在传入一个实现了Runnable的线程实例target给Thread后Thread的run方法在执行时就会调用target.run方法并执行该线程具体的实现逻辑。在JDK源码中run方法的实现代码如下Overridepublicvoidrun(){if(target!null){target.run();}}1.1.3、通过ExecutorService和Callable实现有返回值的线程有时我们需要在主线程中开启多个线程并发执行一个任务然后收集各个线程执行返回的结果并将最终结果汇总起来这时就要用到Callable接口。具体的实现方法为创建一个类并实现Callable接口在call方法中实现具体的运算逻辑并返回计算结果。具体的调用过程为创建一个线程池、一个用于接收返回结果的Future List及Callable线程实例使用线程池提交任务并将线程执行之后的结果保存在Future中在线程执行结束后遍历Future List中的Future对象在该对象上调用get方法就可以获取Callable线程任务返回的数据并汇总结果实现代码如下//step 1通过实现Callable接口创建MyCallable线程publicclassMyCallableimplementsCallableString{privateStringname;publicMyCallable(Stringname){//通过构造函数为线程传递参数以定义线程的名称this.namename;}OverridepublicStringcall()throwsException{//call方法内为线程实现逻辑returnname;}}//step 2创建一个固定大小为5的线程池ExecutorServicepoolExecutors.newFixedThreadPool(5);//step 3创建多个有返回值的任务列表listListFuturelistnewArrayListFuture();for(inti0;i5;i){//step 4创建一个有返回值的线程实例CallablecnewMyCallable(i );//step 5提交线程获取Future对象并将其保存到Future List中Futurefuturepool.submit(c);System.out.println(submit a callable thread:i);list.add(future);}//step 6关闭线程池等待线程执行结束pool.shutdown();//step 7遍历所有线程的运行结果for(Futurefuture:list){//从Future对象上获取任务的返回值并将结果输出到控制台System.out.println(get the result from callable thread:f.get().toString())}1.1.4、基于线程池线程是非常宝贵的计算资源在每次需要时创建并在运行结束后销毁是非常浪费资源的。我们可以使用缓存策略并使用线程池来创建线程具体过程为创建一个线程池并用该线程池提交线程任务实现代码如下//step 1创建大小为10的线程池ExecutorServicethreadPoolExecutors.newFixedThreadPool(10);for(inti0;i10;i){//step 2提交多个线程任务并执行threadPool.execute(newRunnable(){Overridepublicvoidrun(){System.out.println(Thread.currentThread().getName()is running);}});}}2、线程池的工作原理Java线程池主要用于管理线程组及其运行状态以便Java虚拟机更好地利用CPU资源。Java线程池的工作原理为JVM先根据用户的参数创建一定数量的可运行的线程任务并将其放入队列中在线程创建后启动这些任务如果线程数量超过了最大线程数量用户设置的线程池大小​则超出数量的线程排队等候在有任务执行完毕后线程池调度器会发现有可用的线程进而再次从队列中取出任务并执行。线程池的主要作用是线程复用、线程资源管理、控制操作系统的最大并发数以保证系统高效通过线程资源复用实现且安全通过控制最大线程并发数实现地运行。2.1、线程复用在Java中每个Thread类都有一个start方法。在程序调用start方法启动线程时Java虚拟机会调用该类的run方法。前面说过在Thread类的run方法中其实调用了Runnable对象的run方法因此可以继承Thread类在start方法中不断循环调用传递进来的Runnable对象程序就会不断执行run方法中的代码。可以将在循环方法中不断获取的Runnable对象存放在Queue中当前线程在获取下一个Runnable对象之前可以是阻塞的这样既能有效控制正在执行的线程个数也能保证系统中正在等待执行的其他线程有序执行。这样就简单实现了一个线程池达到了线程复用的效果。2.2、线程池的核心组件和核心类Java线程池主要由以下4个核心组件组成。线程池管理器用于创建并管理线程池。工作线程线程池中执行具体任务的线程。任务接口用于定义工作线程的调度和执行策略只有线程实现了该接口线程中的任务才能够被线程池调度。任务队列存放待处理的任务新的任务将会不断被加入队列中执行完成的任务将被从队列中移除。Java中的线程池是通过Executor框架实现的在该框架中用到了Executor、Executors、ExecutorService、ThreadPoolExecutor、Callable、Future、FutureTask这几个核心类具体的继承关系如图所示。其中ThreadPoolExecutor是构建线程的核心方法该方法的定义如下publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueRunnableworkQueue){this(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,Executors.defaultThreadFactory(),defaultHandler);}2.3、Java线程池的工作流程Java线程池的工作流程为线程池刚被创建时只是向系统申请一个用于执行线程队列和管理线程池的线程资源。在调用execute()添加一个任务时线程池会按照以下流程执行任务。如果正在运行的线程数量少于corePoolSize用户定义的核心线程数​线程池就会立刻创建线程并执行该线程任务。如果正在运行的线程数量大于等于corePoolSize该任务就将被放入阻塞队列中。在阻塞队列已满且正在运行的线程数量少于maximumPoolSize时线程池会创建非核心线程立刻执行该线程任务。在阻塞队列已满且正在运行的线程数量大于等于maximumPoolSize时线程池将拒绝执行该线程任务并抛出RejectExecutionException异常。在线程任务执行完毕后该任务将被从线程池队列中移除线程池将从队列中取下一个线程任务继续执行。在线程处于空闲状态的时间超过keepAliveTime时间时正在运行的线程数量超过corePoolSize该线程将会被认定为空闲线程并停止。因此在线程池中所有线程任务都执行完毕后线程池会收缩到corePoolSize大小。具体的流程如图所示。2.4、线程池的拒绝策略若线程池中的核心线程数被用完且阻塞队列已排满则此时线程池的线程资源已耗尽线程池将没有足够的线程资源执行新的任务。为了保证操作系统的安全线程池将通过拒绝策略处理新添加的线程任务。JDK内置的拒绝策略有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy这4种默认的拒绝策略在ThreadPoolExecutor中作为内部类提供。在默认的拒绝策略不能满足应用的需求时可以自定义拒绝策略。2.4.1、AbortPolicyAbortPolicy直接抛出异常阻止线程正常运行具体的JDK源码如下publicstaticclassAbortPolicyimplementsRejectedExecutionHandler{publicAbortPolicy(){}publicvoidrejectedExecution(Runnabler,ThreadPoolExecutore){//直接抛出异常信息不做任何处理thrownewRejectedExecutionException(Task r.toString() rejected from e.toString());}}2.4.2、CallerRunsPolicyCallerRunsPolicy的拒绝策略为如果被丢弃的线程任务未关闭则执行该线程任务。注意CallerRunsPolicy拒绝策略不会真的丢弃任务。具体的JDK实现源码如下publicvoidrejectedExecution(Runnabler,ThreadPoolExecutore){if(!e.isShutdown()){r.run();//执行被丢弃的任务r}}2.4.3、DiscardOldestPolicyDiscardOldestPolicy的拒绝策略为移除线程队列中最早的一个线程任务并尝试提交当前任务。具体的JDK实现源码如下publicvoidrejectedExecution(Runnabler,ThreadPoolExecutore){if(!e.isShutdown()){e.getQueue().poll();//丢弃移除线程队列中最老最后的一个线程任务e.execute(r);//尝试提交当前任务}}2.4.4、DiscardPolicyDiscardPolicy的拒绝策略为丢弃当前的线程任务而不做任何处理。如果系统允许在资源不足的情况下丢弃部分任务则这将是保障系统安全、稳定的一种很好的方案。具体的JDK实现源码如下//直接丢弃线程不做任何处理publicvoidrejectedExecution(Runnabler,ThreadPoolExecutore){}2.4.5、自定义拒绝策略以上4种拒绝策略均实现了RejectedExecutionHandler接口若无法满足实际需要则用户可以自己扩展RejectedExecutionHandler接口来实现拒绝策略并捕获异常来实现自定义拒绝策略。下面实现一个自定义拒绝策略DiscardOldestNPolicy该策略根据传入的参数丢弃最老的N个线程以便在出现异常时释放更多的资源保障后续线程任务整体、稳定地运行。具体的JDK实现源码如下publicclassDiscardOldestNPolicyimplementsRejectedExecutionHandler{privateintdiscardNumber5;privateListRunnablediscardListnewArrayListRunnable();publicDiscardOldestNPolicy(intdiscardNumber){this.discardNumberdiscardNumber;}publicvoidrejectedExecution(Runnabler,ThreadPoolExecutore){if(e.getQueue().size()discardNumber){//step 1批量移除线程队列中的discardNumber个线程任务e.getQueue().drainTo(discardList,discardNumber);discardList.clear();//step 2清空discardList列表if(!e.isShutdown()){e.execute(r);//step 3尝试提交当前任务}}}}3、5种常用的线程池Java定义了Executor接口并在该接口中定义了execute()用于执行一个线程任务然后通过ExecutorService实现Executor接口并执行具体的线程操作。ExecutorService接口有多个实现类可用于创建不同的线程池如表所示是5种常用的线程池。3.1、newCachedThreadPoolnewCachedThreadPool用于创建一个缓存线程池。之所以叫缓存线程池是因为它在创建新线程时如果有可重用的线程则重用它们否则重新创建一个新的线程并将其添加到线程池中。对于执行时间很短的任务而言newCachedThreadPool线程池能很大程度地重用线程进而提高系统的性能。在线程池的keepAliveTime时间超过默认的60秒后该线程会被终止并从缓存中移除因此在没有线程任务运行时newCachedThreadPool将不会占用系统的线程资源。在创建线程时需要执行申请CPU和内存、记录线程状态、控制阻塞等多项工作复杂且耗时。因此在有执行时间很短的大量任务需要执行的情况下newCachedThreadPool能够很好地复用运行中的线程任务已经完成但未关闭的线程资源来提高系统的运行效率。具体的创建方式如下ExecutorServicecachedThreadPoolExecutors.newCachedThreadPool();3.2、newFixedThreadPoolnewFixedThreadPool用于创建一个固定线程数量的线程池并将线程资源存放在队列中循环使用。在newFixedThreadPool线程池中若处于活动状态的线程数量大于等于核心线程池的数量则新提交的任务将在阻塞队列中排队直到有可用的线程资源具体的创建方式如下ExecutorServicefixedThreadPoolExecutors.newFixedThreadPool(5);3.3、newScheduledThreadPoolnewScheduledThreadPool创建了一个可定时调度的线程池可设置在给定的延迟时间后执行或者定期执行某个线程任务ScheduledExecutorServicescheduledThreadPoolExecutors.newScheduledThreadPool(3);//1创建一个延迟3秒执行的线程scheduledThreadPool.schedule(newRunnable(){Overridepublicvoidrun(){System.out.println(delay 3 seconds execu.);}},3,TimeUnit.SECONDS);//2创建一个延迟1秒执行且每3秒执行一次的线程scheduledThreadPool.scheduleAtFixedRate(newRunnable(){Overridepublicvoidrun(){System.out.println(delay 1 seconds, repeat execute every 3 seconds);}},1,3,TimeUnit.SECONDS);3.4、newSingleThreadExecutornewSingleThreadExecutor线程池会保证永远有且只有一个可用的线程在该线程停止或发生异常时newSingleThreadExecutor线程池会启动一个新的线程来代替该线程继续执行任务ExecutorServicesingleThreadExecutors.newSingleThreadExecutor();3.5、newWorkStealingPoolnewWorkStealingPool创建持有足够线程的线程池来达到快速运算的目的在内部通过使用多个队列来减少各个线程调度产生的竞争。这里所说的有足够的线程指JDK根据当前线程的运行需求向操作系统申请足够的线程以保障线程的快速执行并很大程度地使用系统资源提高并发计算的效率省去用户根据CPU资源估算并行度的过程。当然如果开发者想自己定义线程的并发数则也可以将其作为参数传入。4、线程的生命周期线程的生命周期分为新建New​、就绪Runnable​、运行Running​、阻塞Blocked和死亡Dead这5种状态。在系统运行过程中不断有新的线程被创建旧的线程在执行完毕后被清理线程在排队获取共享资源或者锁时将被阻塞因此运行中的线程会在就绪、阻塞、运行状态之间来回切换。线程的具体状态转化流程如图所示。其流程如下所述。调用new方法新建一个线程这时线程处于新建状态。调用start方法启动一个线程这时线程处于就绪状态。处于就绪状态的线程等待线程获取CPU资源在等待其获取CPU资源后线程会执行run方法进入运行状态。正在运行的线程在调用了yield方法或失去处理器资源时会再次进入就绪状态。正在执行的线程在执行了sleep方法、I/O阻塞、等待同步锁、等待通知、调用suspend方法等操作后会挂起并进入阻塞状态进入Blocked池。阻塞状态的线程由于出现sleep时间已到、I/O方法返回、获得同步锁、收到通知、调用resume方法等情况会再次进入就绪状态等待CPU时间片的轮询。该线程在获取CPU资源后会再次进入运行状态。处于运行状态的线程在调用run方法或call方法正常执行完成、调用stop方法停止线程或者程序执行错误导致异常退出时会进入死亡状态。4.1、新建状态New在Java中使用new关键字创建一个线程新创建的线程将处于新建状态。在创建线程时主要是为线程分配内存并初始化其成员变量的值。4.2、就绪状态Runnable新建的线程对象在调用start方法之后将转为就绪状态。此时JVM完成了方法调用栈和程序计数器的创建等待该线程的调度和运行。4.3、运行状态Running新建的线程对象在调用start方法之后将转为就绪状态。此时JVM完成了方法调用栈和程序计数器的创建等待该线程的调度和运行。4.4、阻塞状态Blocked运行中的线程会主动或被动地放弃CPU的使用权并暂停运行此时该线程将转为阻塞状态直到再次进入可运行状态才有机会再次竞争到CPU使用权并转为运行状态。阻塞的状态分为以下三种。等待阻塞在运行状态的线程调用o.wait方法时JVM会把该线程放入等待队列Waitting Queue中线程转为阻塞状态。同步阻塞在运行状态的线程尝试获取正在被其他线程占用的对象同步锁时JVM会把该线程放入锁池Lock Pool中此时线程转为阻塞状态。其他阻塞运行状态的线程在执行Thread.sleep(long ms)、Thread.join()或者发出I/O请求时JVM会把该线程转为阻塞状态。直到sleep()状态超时、Thread.join()等待线程终止或超时或者I/O处理完毕线程才重新转为可运行状态。4.5、线程死亡Dead线程在以下面三种方式结束后转为死亡状态。线程正常结束run方法或call方法执行完成。线程异常退出运行中的线程抛出一个Error或未捕获的Exception线程异常退出。手动结束调用线程对象的stop方法手动结束运行中的线程该方式会瞬间释放线程占用的同步对象锁导致锁混乱和死锁不推荐使用​。5、线程的基本方法线程相关的基本方法有wait、notify、notifyAll、sleep、join、yield等这些方法控制线程的运行并影响线程的状态变化。5.1、线程等待wait方法调用wait方法的线程会进入WAITING状态只有等到其他线程的通知或被中断后才会返回。需要注意的是在调用wait方法后会释放对象的锁因此wait方法一般被用于同步方法或同步代码块中。5.2、线程睡眠sleep方法调用sleep方法会导致当前线程休眠。与wait方法不同的是sleep方法不会释放当前占有的锁会导致线程进入TIMED-WATING状态而wait方法会导致当前线程进入WATING状态。5.3、线程让步yield方法调用yield方法会使当前线程让出释放CPU执行时间片与其他线程一起重新竞争CPU时间片。在一般情况下优先级高的线程更有可能竞争到CPU时间片但这不是绝对的有的操作系统对线程的优先级并不敏感。5.4、线程中断interrupt方法interrupt方法用于向线程发行一个终止通知信号会影响该线程内部的一个中断标识位这个线程本身并不会因为调用了interrupt方法而改变状态阻塞、终止等​。状态的具体变化需要等待接收到中断标识的程序的最终处理结果来判定。对interrupt方法的理解需要注意以下4个核心点。调用interrupt方法并不会中断一个正在运行的线程也就是说处于Running状态的线程并不会因为被中断而终止仅仅改变了内部维护的中断标识位而已。具体的JDK源码如下publicstaticbooleaninterrupted(){returncurrentThread().isInterrupted(true);}publicbooleanisInterrupted(){returnisInterrupted(false);}若因为调用sleep方法而使线程处于TIMED-WATING状态则这时调用interrupt方法会抛出InterruptedException使线程提前结束TIMED-WATING状态。许多声明抛出InterruptedException的方法如Thread.sleep(long mills)在抛出异常前都会清除中断标识位所以在抛出异常后调用isInterrupted方法将会返回false。中断状态是线程固有的一个标识位可以通过此标识位安全终止线程。比如在想终止一个线程时可以先调用该线程的interrupt方法然后在线程的run方法中根据该线程isInterrupted方法的返回状态值安全终止线程。publicclassSafeInterruptThreadextendsThread{Overridepublicvoidrun(){if(!Thread.currentThread().isInterrupted()){try{//1这里处理正常的线程业务逻辑sleep(10);}catch(InterruptedExceptione){Thread.currentThread().interrupt();//重新设置中断标识}}if(Thread.currentThread().isInterrupted()){//2处理线程结束前必要的一些资源释放和清理工作比如释放锁、//存储数据到持久化层、发出异常通知等用于实现线程的安全退出sleep(10);}}}//3定义一个可安全退出的线程SafeInterruptThreadthreadnewSafeInterruptThread();//4安全退出线程thread.interrupt();5.5、线程加入join方法join方法用于等待其他线程终止如果在当前线程中调用一个线程的join方法则当前线程转为阻塞状态等到另一个线程结束当前线程再由阻塞状态转为就绪状态等待获取CPU的使用权。在很多情况下主线程生成并启动了子线程需要等到子线程返回结果并收集和处理再退出这时就要用到join方法具体的使用方法如下System.out.println(子线程运行开始);ChildThreadchildThreadnewChildThread();childThread.join();//等待子线程childThread执行结束System.out.println(子线join()结束开始运行主线程);5.6、线程唤醒notify方法Object类有个notify方法用于唤醒在此对象监视器上等待的一个线程如果所有线程都在此对象上等待则会选择唤醒其中一个线程选择是任意的。我们通常调用其中一个对象的wait方法在对象的监视器上等待直到当前线程放弃此对象上的锁定才能继续执行被唤醒的线程被唤醒的线程将以常规方式与在该对象上主动同步的其他线程竞争。类似的方法还有notifyAll用于唤醒在监视器上等待的所有线程。5.7、后台守护线程setDaemon方法setDaemon方法用于定义一个守护线程也叫作“服务线程”​该线程是后台线程有一个特性即为用户线程提供公共服务在没有用户线程可服务时会自动离开。守护线程的优先级较低用于为系统中的其他对象和线程提供服务。将一个用户线程设置为守护线程的方法是在线程对象创建之前用线程对象的setDaemon(true)来设置。在后台守护线程中定义的线程也是后台守护线程。后台守护线程是JVM级别的比如垃圾回收线程就是一个经典的守护线程在我们的程序中不再有任何线程运行时程序就不会再产生垃圾垃圾回收器也就无事可做所以在回收JVM上仅剩的线程时垃圾回收线程会自动离开。它始终在低级别的状态下运行用于实时监控和管理系统中的可回收资源。守护线程是运行在后台的一种特殊线程独立于控制终端并且周期性地执行某种任务或等待处理某些已发生的事件。也就是说守护线程不依赖于终端但是依赖于JVM与JVM“同生共死”​。在JVM中的所有线程都是守护线程时JVM就可以退出了如果还有一个或一个以上的非守护线程则JVM不会退出。至此对影响线程的核心方法基本介绍完毕各方法对线程状态的影响如图所示。5.8、sleep方法与wait方法的区别sleep方法与wait方法的区别如下。sleep方法属于Thread类wait方法则属于Object类。sleep方法暂停执行指定的时间让出CPU给其他线程但其监控状态依然保持在指定的时间过后又会自动恢复运行状态。在调用sleep方法的过程中线程不会释放对象锁。在调用wait方法时线程会放弃对象锁进入等待此对象的等待锁池只有针对此对象调用notify方法后该线程才能进入对象锁池准备获取对象锁并进入运行状态。5.9、start方法与run方法的区别start方法与run方法的区别如下。start方法用于启动线程真正实现了多线程运行。在调用了线程的start方法后线程会在后台执行无须等待run方法体的代码执行完毕就可以继续执行下面的代码。在通过调用Thread类的start方法启动一个线程时此线程处于就绪状态并没有运行。run方法也叫作线程体包含了要执行的线程的逻辑代码在调用run方法后线程就进入运行状态开始运行run方法中的代码。在run方法运行结束后该线程终止CPU再调度其他线程。5.10、终止线程的4种方式5.10.1、正常运行结束指线程体执行完成线程自动结束。5.10.2、使用退出标志退出线程在一般情况下在run方法执行完毕时线程会正常结束。然而有些线程是后台线程需要长时间运行只有在系统满足某些特殊条件后才能触发关闭这些线程。这时可以使用一个变量来控制循环比如设置一个boolean类型的标志并通过设置这个标志为true或false来控制while循环是否退出具体的实现代码如下publicclassThreadSafeextendsThread{publicvolatilebooleanexitfalse;publicvoidrun(){while(!exit){//执行业务逻辑代码}}}以上代码在线程中定义了一个退出标志exit, exit的默认值为false。在定义exit时使用了一个Java关键字volatile这个关键字用于使exit线程同步安全也就是说在同一时刻只能有一个线程修改exit的值在exit为true时while循环退出。5.10.3、使用Interrupt方法终止线程使用interrupt方法终止线程有以下两种情况。1线程处于阻塞状态。例如在使用了sleep、调用锁的wait或者调用socket的receiver、accept等方法时会使线程处于阻塞状态。在调用线程的interrupt方法时会抛出InterruptException异常。我们通过代码捕获该异常然后通过break跳出状态检测循环可以有机会结束这个线程的执行。通常很多人认为只要调用interrupt方法就会结束线程这实际上理解有误一定要先捕获InterruptedException异常再通过break跳出循环才能正常结束run方法。具体的实现代码如下publicclassThreadSafeextendsThread{publicvoidrun(){while(!isInterrupted()){//在非阻塞过程中通过判断中断标志来退出try{Thread.sleep(51000);//在阻塞过程中捕获中断异常来退出}catch(InterruptedExceptione){e.printStackTrace();break;//在捕获到异常后执行break跳出循环}}}}2线程未处于阻塞状态。此时使用isInterrupted方法判断线程的中断标志来退出循环。在调用interrupt方法时中断标志会被设置为true并不能立刻退出线程而是执行线程终止前的资源释放操作等待资源释放完毕后退出该线程。5.10.4、使用stop方法终止线程不安全在程序中可以直接调用Thread.stop方法强行终止线程但这是很危险的就像突然关闭计算机的电源而不是正常关机一样可能会产生不可预料的后果。在程序使用Thread.stop方法终止线程时该线程的子线程会抛出ThreadDeatherror错误并且释放子线程持有的所有锁。加锁的代码块一般被用于保护数据的一致性如果在调用Thread.stop方法后导致该线程所持有的所有锁突然释放而使锁资源不可控制被保护的数据就可能出现不一致的情况其他线程在使用这些被破坏的数据时有可能使程序运行错误。因此并不推荐采用这种方法终止线程。