Thread类的介绍

发布时间:2026/7/5 21:00:54

Thread类的介绍 线程是操作系统中的概念操作系统中的内核实现了线程这种机制同时操作系统也提供了一些关于线程的API让程序员来创建和使用线程。在JAVA中Thread类就可以被视为是对操作系统中提供一些关于线程的API的的进一步的封装。多线程程序的特点1.每一个线程都是一个执行流2.CPU对线程的执行是并发执行同时对线程的执行也是随机调度的。1.创建线程1. 通过Thread类创建线程class MyThread extends Thread{ Override public void run() { while(true){ System.out.println(hello Thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Demo1 { public static void main(String[] args) throws InterruptedException { Thread t new MyThread(); t.start(); while(true){ System.out.println(hello main); Thread.sleep(1000); } } }我们通过创建一个Thread类的子类MyThread重写Thread类中的run方法并通过MyThread子类来创建一个线程的实例化对象run方法是线程的入口函数。而在主函数中的start方法表示创建一个线程且一个线程只能start一次。当我们运行上面代码发现打印的内容的顺序会变换这是因为多线程的影响上面代码中有两个线程分别位t线程和主线程由于线程在CPU上是并发执行的且又因为CPU对线程又是随机调度的这就导致我们两个线程的执行顺序会改变自然而然打印的顺序也会改变。2.通过Runnable接口来创建线程class MyRunnable implements Runnable{ Override public void run() { while (true){ System.out.println(hello t); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Demo2 { public static void main(String[] args) { Runnable runnablenew MyRunnable(); Thread tnew Thread(runnable); t.start(); } }3. 通过匿名内部类来创建线程匿名内部类的写法本质完成了3件事1.创建了一个Thread的子类不知到该子类的名字所以为匿名。2.{ }代码块里面可以编写子类的定义代码需要哪些属性需要重写哪些父类的方法等等。3. 创建了匿名内部类的实例并将该实例传给了t1.3.1 Threadpublic static void main(String[] args) throws InterruptedException { Thread t1new Thread(){ Override public void run() { while(true){ System.out.println(hello t); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; t1.start(); while (true){ System.out.println(hello main); Thread.sleep(1000); } }3.2 Runnable接口public static void main(String[] args) throws InterruptedException { Runnable runnablenew Runnable() { Override public void run() { while(true){ System.out.println(hello t); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread tnew Thread(runnable); t.start(); while (true){ System.out.println(hello main); Thread.sleep(1000); } }4.通过Lambda表达式式创建线程-----()-{}由于Java中方法必须依附于类的体系中由于Lambda表达式本质就是一个回调函数所以为了快速实现这个回调函数在Java中就创建了一个函数式接口-----()-{ },{ }代码块可以写上回调函数所需要功能的代码。public static void main(String[] args) throws InterruptedException { Thread tnew Thread(()-{ while (true){ System.out.println(hello t); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); while (true){ System.out.println(hello main); Thread.sleep(1000); } }5.实现Callable接口与FutureTaskjava.util.concurrent.Callable接口类似于Runnable但Callable的call()方法可以有返回值并且可以抛出异常。要执行Callable任务需将它包装进一个FutureTask因为Thread类的构造器只接受Runnable参数而FutureTask实现了Runnable接口。package com.xzt.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; class MyCallable implements CallableInteger{ Override public Integer call() throws Exception { int sum0; for(int i0;i101;i){ sumi; } return sum; } } public class demo4 { public static void main(String[] args) throws ExecutionException, InterruptedException { MyCallable tasknew MyCallable(); FutureTaskInteger futureTask new FutureTask(task); Thread tnew Thread(futureTask); t.start(); Integer resultfutureTask.get(); System.out.println(result:result); } }6.使用线程池Executor框架从Java 5开始引入的java.util.concurrent.ExecutorService和相关类提供了线程池的支持这是一种更高效的线程管理方式避免了频繁创建和销毁线程的开销。可以通过Executors类的静态方法创建不同类型的线程池。class Task implements Runnable{ Override public void run() { String threadName Thread.currentThread().getName(); System.out.println(线程 threadName 输出1111); } } public class demo5 { public static void main(String[] args) { ExecutorService executor Executors.newFixedThreadPool(10); for (int i0;i100;i){ executor.submit(new Task()); } executor.shutdown(); } }缺点程池增加了程序的复杂度特别是当涉及线程池参数调整和故障排查时。错误的配置可能导致死锁、资源耗尽等问题这些问题的诊断和修复可能较为复杂。优点线程池可以重用预先创建的线程避免了线程创建和销毁的开销显著提高了程序的性能。对于需要快速响应的并发请求线程池可以迅速提供线程来处理任务减少等待时间。并且线程池能够有效控制运行的线程数量防止因创建过多线程导致的系统资源耗尽如内存溢出。通过合理配置线程池大小可以最大化CPU利用率和系统吞吐量。2.Thread类及常见方法Thread类是JVM用来管理线程的一个类也就是说每个线程都有唯一的Thread类对象与之相关联。在Java中每一个执行流线程都要用一个对象来表示而Thread类对象就可以用来表示一个线程执行流JVM会将这些Thread类对象组织起来进行线程调度和线程管理。2.1 Thread类常见的构造方法方法说明Thread()创建线程对象Thread(Runnable target)使用Runnable对象创建线程对象Thread(String name)创建线程对象并给线程命名Thread(Runnable target,String name)使用Runna对象创建线程对象并命名【了解】Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理分好的组即为线程组注释关于线程的名字我们可以通过Jconsole来观察。2.2 Thread的几个常见属性属性获取方法IDgetID()名称getName()状态getState()优先级getPriority()是否为后台线程isDaemon()是否存活isAlive()是否被中断isInterrupted()1.ID是线程的唯一标识不同线程的ID不会重复2.名称是各种调试工具用到3.状态表示线程当前所处的一个状态4.优先级高的线程理论上更容易被CPU调度到5.关于后台线程线程分为后台线程守护线程和前台线程用户线程。我们需要记住一点JVM会在一个线程中的所有的前台线程结束之后JVM才会选择退出程序终止执行。也就是说后台线程的存在不会阻止JVM的终止相反前台线程的存在会阻止JVM的终止即使主线程已经结束。在Java中线程在默认情况下是后台线程例如垃圾回收机制就是一种后台线程。但是我们可以通过调用setDaemon(true)方法将一个线程设置为后台线程。6.是否存活可以简单理解为run方法是否运行结束。2.3 如何启动一个线程----start()一个线程对象被创建出来并不意味着线程就开始运行了。我们需要去调用start方法接着线程中的run方法就会自动执行只有run方法执行以后一个线程才是真正运行起来。而调用了start方法才是真正的在操作系统的层面建立了一个线程。如下图run方法和start方法的区别1.功能作用不同a. run方法的作用是描述线程具体要执行的任务b. start方法的作用是真正的在操作系统层面建立一个线程2.运行结果不同a. run方法是一个类中的普通方法主动调用和调用pt方法一样会顺序执行一次b. start方法被调用后start方法内部会调用Java本地方法封装了对系统底层的调用真正得启动线程并执行run方法中得代码run方法执行完成后线程进入销毁阶段。2.4 中断一个线程中断一个线程就是让该线程直接停止掉不会再回复。中断一个线程就是让该线程的run方法入口方法尽快结束掉。目前常见终止线程的方式有以下两种方式1.通过共享的标记来沟通2.调用interrupt()方法来通知1.通过共享的标记来沟通该方法是通过设计一个外部成员变量来终止一个线程。如以下代码public class Demo9 { public static boolean isFinishedfalse;//共享标记 public static void main(String[] args) throws InterruptedException { Thread tnew Thread(()-{ while (!isFinished){ System.out.println(hello t); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); Thread.sleep(3000); System.out.println(main尝试终止线程t); isFinishedtrue; } }我们通过isFinished 这个外部成员变量的值来通知操作系统是否终止t线程。运行结果注意事项我们不能将该外部成员变量设置成一个局部变量。否则会报出以下错误原因解释这就涉及到变量捕获的问题 。简单来说就是lambda表达式希望可以使用外面的变量触发“变量捕获”的语法。由于lambda是一个回调函数执行时机是很久以后当操作系统真正创建出线程之后才会执行。很有可能在创建线程的过程中main线程就已经运行结束了,自然而然身为局部变量的isFinished就会被销毁了。为了解决这个问题Java中的做法是将被捕获的变量拷贝一份到lambda里面外面变量的是否销毁就不影响lambda里面的执行了。拷贝就以为着该变量就不适合被修改尽管你在外面修改了这一变量的值也不会影响拷贝到内部的值本质上被拷贝的变量和拷贝的变量是两个变量这种一边变一边不变可能给程序员带来更多的疑惑。所以在Java中就规定了拷贝的变量就压根不允许被改变。所以后面修改isFinished的值时会报一个isFinished should be final or effectively final 的错因为在Java中被final修饰的变量是无法被修改的。那为什么将isFinished设置为成员变量会没事呢因为当isFinished是一个成员变量时此时触发的语法不在是“变量捕获”而是切换成“内部类访问外部类的成员”的语法。那是lambda表达式本质上是一个函数式接口也相当于是一个内部类。isFinished本身就是一个外部类成员内部类本来就能够访问外部类的成员。由于成员变量的生命周期是让GC垃圾回收来管理的GC可以自动识别不在被引用的的对象并将其占用的内存空间释放掉。所以在lambda里面就不必担心变量生命周期失效的问题也就不必拷贝也就不必有被final修饰的限制。2.使用interrupt()方法来通知Java的Thread类中提供了现成的方法直接进行判定线程方法是否被终止不需要我们自己在创建了。方法说明public void interrupt()中断对象关联的线程如果线程正在阻塞则以异常方式通知否则设置中断标志位public static boolean interrupted()判断当前线程的中断标志位是否设置调用后清除标志位不建议使用静态方法位所有线程共有的public boolean isInterrupted()判断对象关联的线程标志位是否设置调用后不清除标志位thread收到通知的方式有两种1.如果线程因为调用wait/join/sleep等方法而阻塞挂起则以InterruedException异常的形式通知清楚中断标志 当出现InterruedException的时候要不要结束进程取决于catch中代码的写法可以忽略该异常也可以跳出循环结束进程。2.否则只是内部的一个中断标志位被设置了可以通过isInterrupted()方法判断中断标志是否被设置不清除中断标志这种方法通知收到的更及时即使线程在sleep也可以马上收到。public class Demo10 { public static void main(String[] args) throws InterruptedException { Thread tnew Thread(()-{ while (!Thread.currentThread().isInterrupted()){ System.out.println(hello t); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace();//报异常清楚中断标志 //break;//跳出循环结束进程 //啥都不写忽略异常进程继续执行 } } }); t.start(); Thread.sleep(3000); System.out.println(main尝试终止线程t); t.interrupt(); } }注意事项针对上述代码由于每次循环线程大部分时间在处sleep状态当主线程调用interrup()方法时会大概率唤醒sleep方法 sleep方法就会报InterruedException异常。正常来说调用interrupt()方法就会将isInterrupted()方法内部的标志位改为true但是上述代码能够把sleep()方法唤醒sleep方法在唤醒之后就会将isInterrupted()方法内部的标志位的值重新改为false。因此在进程不结束的情况下如果继续执行到循环的条件判断就会发现能够继续执行循环中的代码。2.5 等待一个线程-----join()有时我们需要等到一个线程完成它的所有操作后才进行下一步操作。这时候就用到了join()方法。方法说明public void join()等待线程结束public void join(long millis)等待线程结束最多等待millis毫秒public void jooin(long millis,int nanos)同理当精度更高public class Demo11 { public static void main(String[] args) throws InterruptedException { Thread tnew Thread(()-{ for(int i0;i3;i){ System.out.println(hello t); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); System.out.println(main线程在等t线程结束); t.join(); System.out.println(t线程结束main线程结束); } }上面的代码中主线程中线程对象t调用了join方法这就意味着main线程必须要先等待t线程结束后才会执行main线程里面的内容。运行代码如果调用的是join(long millis)版本则会表示main线程只会等待t线程millis秒不管t线程在这段时间内是否终止main线程不会在等待t线程而是继续执行自己的任务。public class Demo11 { public static void main(String[] args) throws InterruptedException { Thread tnew Thread(()-{ for(int i0;i3;i){ System.out.println(hello t); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); System.out.println(main线程在等t2秒); t.join(2000); System.out.println(2秒内t线程没有结束main线程不在等待执行main线程); } }2.6 获取当前线程引用方法说明public static ThreadcurrentThread()返回当前线程对象的引用2.7 休眠当前线程-----sleep()方法说明public static void sleep(long millis)休眠当前线程millis毫秒public static void sleep(long millis,int nanos)可以获得更高精度的睡眠关于sleep方法有一点我们需要记得因为线程调度是不可控的所以sleep方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。这是因为调用sleep方法相当于让当前线程让出cpu的资源。后续休眠时间结束的时候该线程需要操作系统内核时操作系统会将该线程重新调度到cpu上该线程才能继续执行。换言之sleep的时间到了意味着该线程可以被调度而不是立即执行所以实际休眠时间会大于等于参数设置的休眠时间。特殊用法sleep(0)sleep(0)意味着让当前线程立刻放弃CPU资源等待操作系统重新调度。3.线程状态从操作系统的角度来看进程的有就绪和阻塞的两种状态。Java线程也是对操作系统中的线程的重新封装。所以针对线程的状态Java中也重新进行了封装。状态说明NEWThread对象已经创建但是start()方法没有被调用TerminatedThread对象还在但是内核中线程已经结束Runnable就绪状态线程正在CPU上执行或者线程可以随时去CPU上执行TIME_WAITING线程阻塞阻塞的时间有上限。一般是由于join(时间)sleep(时间)产生的阻塞。WAITING死等没事时间限制的等待。一般是由于join(),wait()产生的阻塞BLOCKED由于锁竞争产生的阻塞

相关新闻