行业资讯
Java Thread 类基础用法完整总结 —— 线程创建、中断、等待、休眠、线程实例获取、分清为start/run,避开sleep坑点
目录1. 线程创建1.1 继承Thread类1.2 实现Runnable接口1.3 Lambda简化创建线程2. 线程中断2.1 interrupt() 标记中断2.2 isInterrupted() 判断中断标记2.3 interrupted() 清除中断标记3. 线程等待3.1 join() 等待线程执行完毕3.2 join(long) 限时等待4. 线程休眠4.1 Thread.sleep() 静态休眠方法4.2 sleep中断异常处理5. 获取线程实例5.1 Thread.currentThread() 获取当前线程5.2 线程名称、ID获取前言本文全面总结Java Thread类五大核心基础用法包含代码示例、底层原理与易错点适合Java线程入门学习。1. 线程创建1.1 继承Thread类Java中第一种创建线程的方式是通过继承,写法很简单只是Java不支持多继承,一旦继承Thread类之后无法继承多个类;方式一:通过继承并重写Thread中的方法classMyThreadextendsThread{Overridepublicvoidrun(){System.out.println(我是MyThread);}}publicclassdemo{publicstaticvoidmain(String[]args){MyThreadmyThreadnewMyThread();myThread.start();System.out.println(我是main);}}新建类继承Thread父类重写run()方法写入线程需要执行的代码创建自定义线程类对象调用对象start()方法启动线程我们启动线程调用的是start方法而不是我们重写的run方法此时,就代表我们成功开启并且顺利结束了子线程;当然面试的时候面试官也有可能会问你,为什么调用的是start方法而不是run方法?run()只是封装线程业务逻辑的普通成员方法不具备创建新线程的底层能力。如果手动调用线程对象.run()代码只会在当前主线程串行执行不存在多线程并发。start()是 Thread 类原生启动方法底层调用 native 本地方法完成两步核心操作向操作系统申请线程资源创建一条全新独立的执行线程操作系统调度就绪后JVM会自动回调执行该线程的run()方法。我们需要注意的是同一个线程的start方法只能调用一次,如果重复调用当我们执行的时候就会抛出异常;如:为了更好的理解,我们将代码修改成一个死循环classMyThreadextendsThread{Overridepublicvoidrun(){while(true){System.out.println(我是MyThread);}}}publicclassdemo{publicstaticvoidmain(String[]args){MyThreadmyThreadnewMyThread();myThread.run();while(true){System.out.println(我是main);}}}此时我们能看见只要一个数据在进行输出我们也可以去JavaJDK中bin目录下寻找jconsole.exe这一个可执行文件;我们从始至终都只看见一个main线程;因此,此时我们调用run方法是不会创建线程;在此基础上我们也可以采用匿名内部类的方式来进行创建线程publicclassdemo{publicstaticvoidmain(String[]args){ThreadthreadnewThread(){Overridepublicvoidrun(){while(true){System.out.println(我是MyThread);}}};thread.start();while(true){System.out.println(我是main);}}}这样创建也能达到相同的效果,并且简化了我们的代码; 返回目录1.2 实现Runnable接口Runnable是 Java 提供的函数式接口仅包含一个抽象方法void run()专门用于封装线程要执行的任务逻辑。相比继承Thread类实现Runnable是开发中推荐的线程创建方式。使用Runnable函数式接口更加解耦。classMyThreadimplementsRunnable{Overridepublicvoidrun(){while(true){System.out.println(我是MyThread);}}}publicclassdemo{publicstaticvoidmain(String[]args){ThreadthreadnewThread(newMyThread());thread.start();while(true){System.out.println(我是main);}}}自定义类implements Runnable重写run()方法写入线程业务逻辑创建自定义任务类实例将任务实例传入Thread构造器创建线程对象调用线程对象的start()启动线程自动执行 run 方法使用匿名内部类的方式publicclassdemo{publicstaticvoidmain(String[]args){ThreadthreadnewThread(newRunnable(){Overridepublicvoidrun(){while(true){System.out.println(我是MyThread);}}});thread.start();while(true){System.out.println(我是main);}}}这两种创建方式都能达到简化代码的效果,当然最简化代码的是Lambda表达式 返回目录1.3 Lambda简化创建线程使用Lambda表达式前置条件:函数式接口接口中仅有一个抽象方法无需关心接口名、方法名只需要关注方法入参、返回值Runnable.run()无参数、无返回值对应 Lambda 标准格式() - { 线程任务代码 }当我们{ }中只有一条语句的时候,{ }可以省略不写;publicclassdemo{publicstaticvoidmain(String[]args){ThreadthreadnewThread(()-{while(true){System.out.println(我是MyThread);}});thread.start();while(true){System.out.println(我是main);}}}仅支持函数式接口只能有一个抽象方法不能用于普通多抽象方法接口Lambda 内部访问局部变量时变量必须是final或有效 final不能二次修改)我们这样创建线程就会简便很多;当然在上述情况下我们创建的线程名字都是默认的我们可以在创建线程的时候在后面加上我们要给线程赋予的名字例如,我们想给线程名字起为:ThreadMain当线程创建好之后,我们能在jconsole中进行查看 返回目录2. 线程中断当一个线程执行到一半的时候,我们不想让这个线程执行到后面的代码,此时我们应该怎么办?Java官方并没有给我们提供强制终止线程的API,因为强制终止线程是一个非常危险的方法;我们可以使用中断标记协作式停止核心依靠三个方法interrupt()、isInterrupted()、Thread.interrupted()。 返回目录2.1 interrupt() 标记中断调用目标线程对象的interrupt()方法仅给线程设置中断标志位不会立刻停止线程只是发送 “请求中断” 的信号线程是否退出由自身业务逻辑决定。如:ThreadtnewThread(()-{while(true){// 业务循环}});t.start();t.interrupt();这段代码在执行的时候只会修改内部的标志位,而不会让代码主动停止下来;例子二:ThreadtnewThread(()-{try{Thread.sleep(1000);}catch(InterruptedExceptione){Thread.currentThread().interrupt();System.out.println(线程中断);}});t.start();t.interrupt();此时我们调用了Thread中sleep方法进行休眠1秒钟;在调用t.interrupt的时候,会将Thread内置的标志位从默认false修改为true当线程被中断时,会唤醒sleep,这个sleep非常的坑,后续我们会讲到;除了调用interrupt()由 JVM 内置中断标记外我们也可以自定义布尔变量作为中断标志实现手动控制线程停止。privatestaticvolatilebooleanisStopfalse;publicstaticvoidmain(String[]args){ThreadworknewThread(()-{while(!isStop){System.out.println(任务运行中);try{Thread.sleep(500);}catch(InterruptedExceptione){e.printStackTrace();}}System.out.println(收到停止信号线程退出);});work.start();ScannerscannernewScanner(System.in);intascanner.nextInt();if(a0){isStoptrue;}}我们设置的是全局变量,那如果我们设置的是局部变量呢?Java 语法规则Lambda 表达式 / 匿名内部类访问方法内局部变量时局部变量必须是final或 effectively final有效 final。boolean isStop false;是 main 方法局部变量后续如果想在主线程修改isStop true;变量就不再是有效 final编译器直接报红报错哪怕你不修改这个变量仅读取语法上允许但无法外部手动修改标志位停止线程失去手动标记的意义。有效 final:变量初始化后没有任何重新赋值操作编译器自动视为有效 final允许 Lambda / 匿名内部类访问一旦出现变量 新值赋值语句直接破坏有效 final编译报错。 返回目录2.2 isInterrupted() 判断中断标记publicbooleanisInterrupted()isInterrupted作用:读取线程当前的中断标记状态读取后不会清除标记是业务循环中最常用的中断判断方式。publicstaticvoidmain(String[]args){ThreadtnewThread(()-{while(!Thread.currentThread().isInterrupted()){System.out.println(业务执行中...);}});t.start();ScannerscnewScanner(System.in);intasc.nextInt();if(a0){t.interrupt();}}此时当我们输入给a的值为0的时候,此时interrupt会将内置的标志位改为true,此时线程里面的循环就会停止下来;实例方法必须通过线程对象 /currentThread()调用只查询标记不修改、不清空中断状态适合长轮询、循环任务做退出判断 返回目录2.3 interrupted() 清除中断标记publicstaticbooleaninterrupted()作用:静态方法判断当前执行该方法的线程是否带有中断标记调用完成后自动清空中断标记。例如:我们先给当前线程打上标记位:publicstaticvoidmain(String[]args){// 给当前线程打上中断标记Thread.currentThread().interrupt();System.out.println(Thread.interrupted());System.out.println(Thread.interrupted());}适用场景: 当我们需要一次性消费中断信号消费后不再保留中断状态时使用大部分业务场景优先用isInterrupted()。 返回目录3. 线程等待3.1 join() 等待线程执行完毕有时候,我们并不一定是想要中断一个线程,而是等待一个线程执行完,在轮到这个线程进行执行;举个很常见的例子:在橘子汁加工厂中,假设只有榨汁,装瓶两个操作;我们想要一杯果汁,只有在榨汁完的前提之下,才能进行装瓶这一个操作;如果装瓶这一个操作等待榨汁这一个过程,就可能会出现问题;publicvoidjoin()作用:等待线程结束注意:在哪一个线程中调用对应的join()哪一个线程就会阻塞publicstaticvoidmain(String[]args)throwsInterruptedException{ThreadjuicernewThread(()-{System.out.println(开始榨汁...);try{Thread.sleep(2000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(榨汁完成);});juicer.start();juicer.join();System.out.println(开始装瓶);}这样才能保证,先榨汁后装瓶,因为操作系统的调用是随机的,我们不能保证操作系统先调用哪一个线程 返回目录3.2 join(long) 限时等待带超时时间的重载方法最多等待指定毫秒时长publicvoidjoin(longmillis)若目标线程在超时前执行完毕当前线程提前解除阻塞若等待时长耗尽目标线程仍未结束当前线程不再等待直接向下执行。再举一个例子:小萌和小宇一天晚上说好了一起去逛街,第二天小宇去小萌家小区门口等小萌,小宇最多等小萌3秒钟而小萌,吃早餐和打扮需要5秒钟,如果超时了,小宇就直接打电话了publicstaticvoidmain(String[]args)throwsInterruptedException{Threadt1newThread(()-{System.out.println(开始打扮...);try{Thread.sleep(5000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(小萌打扮完成);});t1.start();//小宇开始等t1.join(3000);//此时小萌还没下来,小宇就直接上去找了System.out.println(小宇等不住了,直接去找小萌);}我们假设榨汁时间为5秒钟,但是装瓶的时候我们只等你3秒钟,3秒钟超时了,我就不等你了;join(long millis, int nanos)精确到纳秒级别的限时等待日常开发极少使用。 返回目录4. 线程休眠4.1 Thread.sleep() 静态休眠方法线程休眠用于让当前线程暂停指定时长让出CPU执行权进入阻塞状态核心API为静态方法Thread.sleep()public static void sleep(long millis)休眠指定毫秒public static void sleep(long millis, int nanos)毫秒 纳秒高精度休眠主要作用:静态方法作用于当前正在执行该代码的线程不是调用某个线程对象休眠阻塞状态休眠期间线程放弃 CPU 资源不参与调度不会释放锁若线程持有synchronized锁休眠时锁依旧占用其他线程无法获取示例:publicstaticvoidmain(String[]args){System.out.println(主线程开始执行);try{Thread.sleep(3000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(3秒后唤醒);} 返回目录4.2 sleep中断异常处理这里的异常处理就非常的坑人了,线程处于sleep()休眠阻塞时若其他线程调用该线程的interrupt()会立刻抛出InterruptedException同时自动清空中断标记。也就是当我们提前中断线程的时候会将默认的标志位从false修改为true而当唤醒sleep的时候,会将线程中我们修改的标志位再从true改回为false这个操作就非常的坑人了,稍有不注意,就会出现线程安全问题;例如:publicstaticvoidmain(String[]args){ThreadthreadnewThread(()-{while(!Thread.currentThread().isInterrupted()){System.out.println(我是MyThread);try{Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println(MyThread被中断了);}}});thread.start();ScannerscnewScanner(System.in);intasc.nextInt();if(a0){thread.interrupt();}}此时我们就发现就算我们想要提前中断当前线程,我们发现根本就没有用,线程中的死循环还是在执行. 返回目录5. 获取线程实例5.1 Thread.currentThread() 获取当前线程开发中经常需要拿到当前运行线程的对象用于操作线程名称、状态、中断标记等Thread类提供静态方法获取当前线程实例配套实例方法读取线程ID与名称。publicstaticThreadcurrentThread()静态方法返回正在执行该代码片段的线程对象与调用位置绑定在哪个线程中调用就返回哪一个线程的实例publicstaticvoidmain(String[]args){ThreadmainThreadThread.currentThread();System.out.println(主线程mainThread);}结果如下: 返回目录5.2 线程名称、ID获取方法功能说明long getId()获取线程唯一标识 IDJVM 自动分配不可修改String getName()获取线程名称默认格式Thread-数字void setName(String name)修改线程自定义名称便于日志区分publicstaticvoidmain(String[]args){ThreadtnewThread(()-{ThreadcurrThread.currentThread();// 获取ID与名称longidcurr.getId();Stringnamecurr.getName();System.out.printf(IDid名称name);},订单处理线程);t.start();System.out.println(外部读取线程名t.getName());}注意:线程 ID 是全局唯一数值线程销毁后不会复用 返回目录
郑州网站建设
网页设计
企业官网