如何建一个免费的网站,微营销课程,外贸网站建设服务平台,wordpress大门户主题1、前言 这篇笔记是我花的20多天跟着⿊⻢的并发编程学习做的笔记#xff0c;地址是b站 ⿊⻢ 并发编程 #xff0c;也是我第⼀次 学习并发 编程#xff0c;所以如果有很多原理讲不清楚的#xff0c;知识点不全的#xff0c;也请多多包涵 中间多数图都是直接截⽼师的笔记…1、前言 这篇笔记是我花的20多天跟着⿊⻢的并发编程学习做的笔记地址是b站 ⿊⻢ 并发编程 也是我第⼀次 学习并发 编程所以如果有很多原理讲不清楚的知识点不全的也请多多包涵 中间多数图都是直接截⽼师的笔记代码有时会跟着敲笔记跟着做的也有些⾃⼰的想法和总结在⾥ ⾯。 2、进程线程 2.1进程与线程 进程进程是代码在数据集合上的一次运行活动是系统资源分配和调度的基本单位。线程线城是进程的一个执行路径一个进程中至少有一个线程进程中的多个线程共享进程的资源。 辅助解释 进程 程序由指令和数据组成但这些指令要运⾏数据要读写就必须将指令加载⾄ CPU数据 加载⾄内存。在指令运⾏过程中还需要⽤到磁盘、⽹络等设备。 ● 进程是⽤来加载指令、管理内存、管理 IO 的 ● 当⼀个程序被运⾏从磁盘加载这个程序的代码⾄内存这时就开启了⼀个进程。 ● 进程就可以视为程序的⼀个实例。⼤部分程序可以同时运⾏多个实例进程例如记事本、画 图、浏览器等也有的程序只能启动⼀个实例进程例如⽹易云⾳乐、360 安全卫⼠等 线程 ● ⼀个进程之内可以分为⼀到多个线程。 ● ⼀个线程就是⼀个指令流将指令流中的⼀条条指令以⼀定的顺序交给 CPU 执⾏ ● Java 中线程作为最⼩调度单位进程作为资源分配的最⼩单位。 ● 在 windows 中进程是不活动的只是作为线程的容器 2.2线程与进程的比较 进程与线程相⽐ ● 进程基本上相互独⽴的⽽线程存在于进程中是进程的⼀个⼦集 ● 进程拥有共享的资源如内存空间等供其内部的线程共享 ● 进程间通信较为复杂 ● 同⼀台计算机的进程通信称为 IPCInter-process communication ● 不同计算机之间的进程通信需要通过⽹络并遵守共同的协议例如 HTTP ● 线程通信相对简单因为它们共享进程内的内存⼀个例⼦是多个线程可以访问同⼀个共享变 量 ● 线程更轻量线程上下⽂切换成本⼀般上要⽐进程上下⽂切换低 线程和进程的区别GPT说 线程和进程都是计算机中的执⾏单元但它们之间有⼏个重要的区别 1. 资源分配⽅式进程是操作系统进⾏资源分配的基本单位每个进程都有⾃⼰的内存空间、⽂件句柄、⽹络 连接等。⽽线程则是在进程内部创建的它们共享进程的内存空间和其他资源。 2. 并发性由于多个线程可以共享进程的内存空间因此它们可以同时运⾏并相互协作从⽽实现更⾼的并发 性。⽽进程之间通常需要使⽤进程间通信IPC来传递数据和同步操作。 3. 系统开销由于进程之间需要独⽴的内存空间和其他资源所以创建和销毁进程时需要较⼤的系统开销。相 ⽐之下创建和销毁线程的开销要⼩得多因为它们共享进程的资源。 4. 执⾏顺序由于线程是在进程内部运⾏的因此它们的执⾏顺序可能会受到⼀些限制。例如在Java中 线程的调度通常是由JVM进⾏控制的⽽且可能⽆法精确地控制线程的执⾏顺序。相⽐之下进程之间的 执⾏顺序通常是可以完全控制的。 总之线程和进程都是实现并发和并⾏的重要概念。线程通常⽤于执⾏轻量级任务以提⾼系统的响应能⼒和吞 吐量⽽进程则更适合执⾏独⽴的、相对较重的任务。 2.3、并发与并行 单核 cpu 下线程实际还是 串⾏执⾏ 的。操作系统中有⼀个组件叫做任务调度器将 cpu 的时 间⽚windows下时间⽚最⼩约为 15 毫秒分给不同的程序使⽤只是由于 cpu 在线程间时 间⽚很短的切换⾮常快⼈类感觉是 同时运⾏的 。总结为⼀句话就是 微观串⾏宏观并⾏ ⼀般会将这种 线程轮流使⽤ CPU 的做法称为并发concurrent 多核 cpu下每个 core 都可以调度运⾏线程这时候线程可以是并⾏的 引⽤ Rob Pike 的⼀段描述 ● 并发concurrent是同⼀时间应对dealing with多件事情的能⼒ ● 并⾏parallel是同⼀时间动⼿做doing多件事情的能⼒ 从操作系统的⾓度来看线程是 CPU 分配的最⼩单位。 并⾏就是同⼀时刻两个线程都在执⾏。这就要求有两个CPU去分别执⾏两个线程。 并发就是同⼀时刻只有⼀个执⾏但是⼀个时间段内两个线程都执⾏了。并发的实现依赖于 CPU切换线程因为切换的时间特别短所以基本对于⽤户是⽆感知的。 2.4应用 同步与异步如何理解 以调⽤⽅⻆度来讲如果 ● 需要等待结果返回才能继续运⾏就是同步 ● 不需要等待结果返回就能继续运⾏就是异步 注意同步在多线程中还有另外⼀层意思是让多个线程步调⼀致 2.4.1异步调⽤ 异步调⽤的核⼼是回调机制当⼀个异步调⽤发起后调⽤⽅不必等待结果返回⽽是可以继续执⾏后 续操作。异 步调⽤会在单独的线程或线程池中执⾏等到异步调⽤完成后会通过回调函数将结果返回给调⽤⽅。 在异步调⽤中调⽤⽅通常需要提供⼀个回调函数⽤于接收异步操作的结果。当异步操作完成后会 直接调⽤ 回调函数并将结果传递给它。这样可以让调⽤⽅在异步操作执⾏的过程中继续执⾏其他任务等到异步 操作完成 后再处理回调结果。这种⽅式可以提⾼程序的并发性和响应速度。 1) 设计 多线程可以让⽅法执⾏变为异步的即不要巴巴⼲等着⽐如说读取磁盘⽂件时假设读取操作花 费了 5 秒钟如果没有线程调度机制这 5 秒 cpu 什么都做不了其它代码都得暂停... 2) 结论 ● ⽐如在项⽬中视频⽂件需要转换格式等操作⽐较费时这时开⼀个新线程处理视频转换避 免阻塞主线程 ● tomcat 的异步 servlet 也是类似的⽬的让⽤户线程处理耗时较⻓的操作避免阻塞 tomcat 的⼯作线程 ● ui 程序中开线程进⾏其他操作避免阻塞 ui 线程 2.4.2多线程提升效率 充分利⽤多核 cpu 的优势提⾼运⾏效率。想象下⾯的场景执⾏ 3 个计算最后将计算结果汇总。 如果是串⾏执⾏那么总共花费的时间是 10 11 9 1 31ms 但如果是四核 cpu各个核⼼分别使⽤线程 1 执⾏计算 1线程 2 执⾏计算 2线程 3 执⾏计算 3 那么 3 个 线程是并⾏的花费时间只取决于最⻓的那个线程运⾏的时间即 11ms 最后加上汇总时间只会花费 12ms ● 单线程和多线程执⾏⼀个任务多线程⽐单线程执⾏速度更快效率更⾼ 注意多核CPU才能提升效率单核还是要依次执⾏ 多线程提升效率的原理是怎样的 多线程提升效率的原理是通过将⼀个程序分成多个独⽴的线程同时运⾏这些线程来完成任务。 每个线程都有⾃⼰的代码执⾏路径和堆栈可以同时运⾏在不同的 CPU 核⼼中。 这样可以最⼤限度地利⽤ CPU 资源从⽽提⾼程序的执⾏效率。 11 多线程执⾏的原理涉及到并发和并⾏两个概念。 ● 并发是指多个线程交替执⾏看起来像是同时执⾏但实际上是在不同的时间⽚中分别执⾏的。 ● 并⾏是指多个线程同时执⾏利⽤了多核 CPU 的优势真正意义上的同时执⾏。 多线程提升效率的关键在于任务的分解和协调。 将⼀个⼤任务分解成多个⼩任务每个⼩任务由⼀个线程独⽴执⾏最后将所有⼩任务的结果合并起来得到最终 的结果。 线程之间需要进⾏协调和通信以避免竞争条件、死锁等问题的发⽣ 当然多线程也存在⼀些缺点⽐如线程之间的协调和通信会增加额外开销如果线程数量过多也会造成资源争 夺和系统负载过重的问题。因此在实际应⽤中需要根据具体情况进⾏合理的线程管理和调度避免出现不必要 的问题。 3.Java线程 3.1.创建和运⾏线程 创建线程 Java程序主⽅法就开启了⼀个线程注意创建线程的时候最好指定⼀个名字Thread t1 new Thread()把线程和任务要执⾏的代码分开 创建线程⽅法1直接使⽤Thread创建线程对象 public class aa extends Thread {public static void main(String[] args) {Thread t new Thread() {Overridepublic void run() {System.out.println(通过Thread⽅式创建线程);}};t.run();//t.start();}
} 加星继承Thead类重写run方法调用start方法启动线程。 public class ThreadTest {/*** 继 承Thread类* /public static class MyThread extends Thread {Overridepublic void run () {System . out . println ( This is child thread ) ;}}public static void main ( String [] args) {MyThread thread new MyThread ();thread .start();}
} 创建线程⽅法2使⽤Runnable配合Thread实现Runnable接口即将任务和线程分离⽐第⼀种更灵活。 Thread 代表线程 Runnable代表可运⾏的任务线程要执⾏的任务 public class aa extends Thread {public static void main(String[] args) {Runnable r new Runnable() {Overridepublic void run() {System.out.println(使⽤Thread配合Runnable创建线程);}};//创建线程对象Thread thread new Thread(r);//启动线程thread.start();}
} Lambda简化创建线程 Tip接⼝带有FunctionInterface注解的函数式接⼝就可以使⽤ Lambda简化 ⼿动简化 ⾃动简化快捷键altenter或者altshiftenter 提示是否要转换成Lambda式idea会有灰⾊ 提示 加星 实现 Runnable 接⼜重写 run() ⽅法 public class RunnableTask implements Runnable {public void run () {System . out . println ( Runnable! ) ;}public static void main ( String [] args) {RunnableTask task new RunnableTask ();new Thread ( task ) .start();}
} 创建线程⽅法3FutureTask 配合 Thread了解 上⾯两种都是没有返回值的但是如果我们需要获取线程的执⾏结果该怎么办呢 后⾯到线程间通信时再说FutureTask 能够接收 Callable 类型的参数⽤来处理有返回结果的情况 Thread与Runnble的关系 分析 Thread 的源码理清它与 Runnable 的关系 看Thread类的源码 ● Thread是⼀个类继承⾃Object类并且实现了Runnable接⼝它代表着⼀个线程。 ● 在Thread类中提供了⼀些⽅法如start()、join()等可以控制线程的⽣命周期和执⾏顺序。 再看Runnbale接⼝的源码 ● Runnable是⼀个接⼝只包含了⼀个run()⽅法它定义了线程所要执⾏的任务。 ● Runnable接⼝通常作为参数传递给Thread类的构造函数让Thread对象来执⾏这个Runnable对象 中的run()⽅法。 ● 这样可以将任务的执⾏和线程的管理分离开来 提⾼代码的可重⽤性和可维护性。 因此Thread与Runnable之间的关系是 ● Thread为Runnable提供了线程的上下⽂环境具体来说就是调⽤Thread.start()⽅法可以启动⼀个新 线程并执⾏Runnable中的run()⽅法。 ● 通过这种⽅式可以实现多线程编程提⾼程序的并发性和效率。 ● 同时将任务和线程分离也符合⾯向对象设计原则中的单⼀职责原则 加星 实现 Callable 接⼜重写 call() ⽅法这种⽅式可以通过 FutureTask 获取任务执⾏的返回值 public class CallerTask implements Callable String {public String call () throws Exception {return Hello,i am running! ;} public static void main ( String [] args) {/ /创建异步任务FutureTask String task new FutureTask String ( new CallerTask ());/ /启动线程new Thread ( task ) .start();try {/ /等 待 执 ⾏ 完 成 并获取返回结果String result task . get();System . out . println ( result) ;} catch ( InterruptedException e ) {e . printStackTrace ();} catch ( ExecutionException e ) {e . printStackTrace ();}}
} 思考 为什么调⽤ start() ⽅法时会执⾏ run() ⽅法那怎么不直接调⽤ run() ⽅ 法 JVM 执⾏ start ⽅法会先创建⼀条线程由创建出来的新线程去执⾏ thread 的 run ⽅法这才起到多线 程的效果。 为什么我们不能直接调⽤ run() ⽅法 也很清楚 如果直接调⽤ Thread 的 run() ⽅法那么 run ⽅法还 是运⾏在主线程中相当于顺序执⾏就起不到多线程的效果。 观察多个线程同时运⾏ 多个线程同时运⾏是指多个线程在同⼀时刻并发地进⾏执⾏。 ● 线程是交替执⾏的 ● 谁先谁后不由我们控制 ● 需要多核CPU单核带不动 具体来说当⼀个程序中有多个线程时这些线程的启动顺序和执⾏顺序可能是不确定的每个线程都 有⾃⼰的执⾏路径和执⾏状态可以在不同的 CPU 核⼼上同时运⾏。 要理解多个线程同时运⾏需要从计算机的硬件和操作系统的⻆度来看待。现代计算机通常包含多个 CPU 核⼼或者是⽀持超线程技术的 CPU这些 CPU 能够并发处理多个指令流。当有多个线程需要执 ⾏时操作系统会将这些线程分配到不同的 CPU 核⼼或者是时间⽚中让它们同时运⾏。同时由于 每个线程都有⾃⼰的代码执⾏路径和堆栈所以它们之间不会相互⼲扰可以独⽴地执⾏各⾃的任务。 3.2.查看进程线程的⽅法 Windows系统 ● 任务管理器可以查看进程和线程数也可以⽤来杀死进程 ● tasklist查看进程 ● taskkill杀死进程 linux系统 ● ps -ef 查看所有进程 ● ps -fT -p 查看某个进程PID的所有线程 ● kill 杀死进程kill -9 进程号 强制杀死进程 ● top 按⼤写 H 切换是否显示线程 ● top -H -p 查看某个进程PID的所有线程 Java程序 ● jps 命令查看所有 Java 进程 ● jstack 查看某个 Java 进程PID的所有线程状态 ● jconsole 来查看某个 Java 进程中线程的运⾏情况图形界⾯ 3.3.线程运⾏原理 栈与栈帧 JVM---Java Virtual Machine Stacks Java 虚拟机栈 我们都知道 JVM 中由堆、栈、⽅法区所组成其中 栈内存 是给谁⽤的呢 其实就是 线程 每个线程启动后虚拟机就会为其分配⼀块栈内存。 Java 虚拟机栈描述的是 Java ⽅法执⾏的线程内存模型⽅法执⾏时 JVM 会同步创建⼀个栈帧⽤来存储局部变量表、操作数栈、动态连接等。 ● 每个栈由多个栈帧Frame组成对应着每次⽅法调⽤时所占⽤的内存 ● 每个线程只能有⼀个活动栈帧对应着当前正在执⾏的那个⽅法 ● 每个栈帧对应⼀个⽅法的执⾏ 线程上下⽂切换Thread Context Switch 线程上下⽂切换简单来说 就是CPU不再执⾏当前线程转⽽执⾏另⼀个线程的代码 下⾯原因会导致线程上下⽂切换 ● 线程的 cpu 时间⽚⽤完 ● 垃圾回收 ● 有更⾼优先级的线程需要运⾏ ● 线程⾃⼰调⽤了 sleep、yield、wait、join、park、synchronized、lock 等⽅法 当 线程上下⽂切换Context Switch 发⽣时需要由 操作系统保存当前线程的状态 并恢复另⼀ 个线程的状态。 Java 中对应的概念就是程序计数器Program Counter Register它的作⽤是记住下⼀条 jvm 指令的执⾏地址是线程私有的 ● 状态包括程序计数器、虚拟机栈中每个栈帧的信息如局部变量、操作数栈、返回地址等 ● 上下⽂切换Context Switch 频繁发⽣会影响性能 3.4线程有哪些常⽤的调度⽅法 线程等待与通知 在 Object 类中有⼀些函数可以⽤于线程的等待与通知。 wait() 当⼀个线程 A 调⽤⼀个共享变量的 wait() ⽅法时 线程 A 会被阻塞挂起 发⽣下⾯⼏种 情况才会返回 1 线程 A 调⽤了共享对象 notify() 或者 notifyAll() ⽅法 2 其他线程调⽤了线程 A 的 interrupt() ⽅法线程 A 抛出 InterruptedException 异常返 回。 wait(long timeout) 这个⽅法相⽐ wait() ⽅法多了⼀个超时参数它的不同之处在于如果线 程A调⽤共享对象的wait(long timeout)⽅法后没有在指定的 timeout ms时间内被其它线程唤 醒那么这个⽅法还是会因为超时⽽返回。 wait(long timeout, int nanos)其内部调⽤的是 wait(long timout函数。 上⾯是线程等待的⽅法⽽唤醒线程主要是下⾯两个⽅法notify() : ⼀个线程A调⽤共享对象的 notify() ⽅法后会唤醒⼀个在这个共享变量上调⽤ wait 系列⽅法后被挂起的线程。 ⼀个共享变量上可能会有多个线程在等待具体唤醒哪个等待的线程 是随机的。 notifyAll() 不同于在共享变量上调⽤ notify() 函数会唤醒被阻塞到该共享变量上的⼀个线程 notifyAll()⽅法则会唤醒所有在该共享变量上由于调⽤ wait 系列⽅法⽽被挂起的线程。 Thread类也提供了⼀个⽅法⽤于等待的⽅法 join()如果⼀个线程A执⾏了thread.join()语句其含义是当前线程A等待thread线程终⽌之 后才 从thread.join()返回。 线程休眠 sleep(long millis) :Thread类中的静态⽅法当⼀个执⾏中的线程A调⽤了Thread 的sleep⽅法 后线程A会暂时让出指定时间的执⾏权但是线程A所拥有的监视器资源⽐如锁还是持有不让 出的。指定的睡眠时间到了后该函数会正常返回接着参与 CPU 的调度获取到 CPU 资源后就 可以继续运⾏。 让出优先权 yield() Thread类中的静态⽅法当⼀个线程调⽤ yield ⽅法时实际就是在暗⽰线程调度器当 前线程请求让出⾃⼰的CPU 但是线程调度器可以⽆条件忽略这个暗⽰。 线程中断 Java 中的线程中断是⼀种线程间的协作模式通过设置线程的中断标志并不能直接终⽌该线程的执 ⾏⽽是被中断的线程根据中断状态⾃⾏处理。 void interrupt() 中断线程例如当线程 A 运⾏时线程 B 可以调⽤线程 interrupt() ⽅法来设 置线程的中断标志为 true 并⽴即返回。设置标志仅仅是设置标志 , 线程 A 实际并没有被中断 会 继续往下执⾏。 boolean isInterrupted() ⽅法 检测当前线程是否被中断。 boolean interrupted() ⽅法 检测当前线程是否被中断与 isInterrupted 不同的是该⽅法如 果发现当前线程被中断则会清除中断标志 线程中常⽤⽅法及功能介绍 1. start和run 调⽤run public static void main(String[] args) {//创建线程Thread t1 new Thread(t1) {Overridepublic void run() {log.debug(Thread.currentThread().getName());FileReader.read(Constants.MP4_FULL_PATH);}};//运⾏线程t1.run();log.debug(do other things ...);
} 程序仍在 main 线程运⾏ FileReader.read() ⽅法调⽤还是同步的 将上⾯代码 t.run(); 改为t.start() 程序在 t1 线程运⾏ FileReader.read() ⽅法调⽤是异步的 run⽅法和start⽅法总结 ● 直接调⽤ run 是在主线程中执⾏了 run没有启动新的线程 ● 使⽤ start 是启动新的线程通过新的线程间接执⾏ run 中的代码 需要注意的是在Java中 不能直接调⽤run()⽅法来启动线程必须使⽤start()⽅法来启动线程。 start()⽅法会为线程创建⼀个新的执⾏路径并在该路径上调⽤run()⽅法。 如果直接调⽤run()⽅法则不会创建新的执⾏路径⽽是在当前线程上执⾏run()⽅法这样就失去了 多线程的意义。 2. sleep和yield Sleeplong n 让当前线程进⼊休眠休眠时CPU的时间⽚会让给其他线程 ● 调⽤sleep⽅法会将线程状态由Runnable-Time_Waiting阻塞状态 ● sleep⽅法在哪调⽤就是是哪个线程睡眠主线程or其他线程 ● interrupt⽅法可以打断正在休眠的线程打断线程后会抛出InterruptedException异常 ● 睡眠结束后的线程未必会得到⽴即执⾏其他线程在运⾏CPU时间⽚不会⽴即分给它 ● 建议使⽤TimeUnit的sleep⽅法代替Thread的sleep⽅法可读性更好 yield() 提示线程调度器让出当前线程对CPU的使⽤ ● 调⽤yield⽅法后会让线程从Running进⼊Runnable就绪状态然后调度执⾏其他线程 ● 具体的实现依赖于操作系统的任务调度器 线程优先级 ● 线程优先级会提示himt调度器优先调度该线程但它仅仅是⼀个提示调度器可以忽略 ● 如果CPU⽐较忙那么优先级越⾼的线程会获得更多的时间⽚但CPU闲时优先级⼏乎没 ⽤ 案例防⽌CPU占⽤100% sleep实现 在没⽤利⽤CPU来计算时不要让whiletrue空转浪费CPU这时可以适应yield或sleep来让出 CPU的使⽤权给其他程序 while(true) {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}
} 可以⽤wait或条件变量达到类似的效果 不同的是后两种都需要加锁并且需要响应的唤醒操作⼀般适⽤于要进⾏同步的场景 sleep适⽤于⽆需锁同步的场景 wait实现 加synchronized锁 synchronized(锁对象) {while(条件不满⾜) {try {锁对象.wait();} catch(InterruptedException e) {e.printStackTrace();}}
// do sth...
} 条件变量实现 加ReentrantLock锁 lock.lock();
try {
while(条件不满⾜) {
try {
条件变量.await();//当前线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// do sth...
} finally {
lock.unlock();
} 3. join sleep⽅法可以使线程休眠那为什么还需要join⽅法 看下⾯这段代码最终输出的结果会是什么 static int r 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {log.debug(开始);Thread t1 new Thread(() - {log.debug(开始);sleep(1);log.debug(结束);r 10;});t1.start();log.debug(结果为:{}, r);log.debug(结束);
} 结果是开始..开始..结果为0..结束..结束..... 为什么会这样呢明明t1线程给r赋值10了啊为什么最后打印出来的r不是10 分析⼀下 主线程和线程 t1 是并⾏执⾏的t1 线程需要 1 秒之后才能算出 r10 ⽽主线程⼀开始就要打印 r 的结果所以只能打印出 r0 解决⽅法 ⽤ sleep ⾏不⾏为什么 不⾏因为sleep要设置休眠时间t1线程具体计算的时间是不确定的sleep要传的参数也不确定 ⽤ join⽅法呢同步 t1.join()加在ti.start()之后就可以解决 join⽅法是让等待当前线程执⾏完才继续向下执⾏ static int result 0;
private static void test1() throws InterruptedException {log.debug(开始);Thread t1 new Thread(() - {log.debug(开始);sleep(1);log.debug(结束);result 10;}, t1);t1.start();t1.join();log.debug(结果为:{}, result);
} 输出 t1线程启动后t1线程调⽤了join⽅法所以主线程需要等待t1线程执⾏完之后才能执⾏ 评价 需要外部共享变量不符合⾯向对象封装的思想 必须等待线程结束不能配合线程池使⽤ 同步怎么理解 以调⽤⽅⻆度来讲 ● 需要等待结果返回才能继续运⾏就是同步 ● 不需要等待结果返回就能继续运⾏就是异步 在上⾯的代码中 主线程同时执⾏t1线程的运⾏和t1线程的join⽅法就是异步 调⽤t1⽅法的join⽅法不需 要等待 t1线程执⾏完才能执⾏ ⽽ 主线程后⾯的打印r的值需要等待t1线程执⾏完就是同步 join⽅法例题 private static void test2() throws InterruptedException {Thread t1 new Thread(() - {sleep(1);r1 10;});Thread t2 new Thread(() - {sleep(2);r2 20;});t1.start();t2.start();long start System.currentTimeMillis();log.debug(join begin);t1.join();log.debug(t1 join end);t2.join();log.debug(t2 join end);long end System.currentTimeMillis();log.debug(r1: {} r2: {} cost: {}, r1, r2, end - start);
} 最后会输出多少 答案是2s 分析如下 第⼀个 join等待 t1 时, t2 并没有停⽌, ⽽在运⾏ 第⼆个 join1s 后, 执⾏到此, t2 也运⾏了 1s, 因此也只需再等待 1s 那如果改变两个join的位置呢 答案也是2s 看流程图 t1的join⽅法在前⾯时主线程异步运⾏t1t2两个线程执⾏调⽤t1的join⽅法后t2线程需要等 待t1线 程执⾏完毕但同时t2线程也在运⾏t1线程运⾏结束等待t2线程运⾏结束这时只需要再等待1s 就可 t2线程已经运⾏了1s总时间2s t2的join⽅法在前⾯同理调⽤t2的join⽅法t1线程等待t2线程运⾏结束的同时也在运⾏所以 t2线程 执⾏完毕后就可以继续向下运⾏总时间2s 等待多个线程的调度 并⾏执⾏多个线程会同时运⾏最终只会消耗时间久的join的时间 有时效的join 线程执⾏会导致join提前结束。 如果开启的t1线程⾥休眠2秒join1.5秒那主线程不会等待t1线程执⾏完就会执⾏。 如果开启的t1线程⾥休眠2秒join3秒那主线程会随着t1线程执⾏完就提前执⾏⽆需等join 完 4.interrupt 打断 sleep,wait,join的线程 这⼏个⽅法都会使线程进⼊阻塞 1、打断sleep的线程会清空打断状态 private static void test1() throws InterruptedException {Thread t1 new Thread(()-{sleep(1);}, t1);t1.start();sleep(0.5);t1.interrupt();log.debug( 打断状态: {}, t1.isInterrupted());
} 总结 在调⽤sleep时执⾏打断会出现异常 sleep在调⽤时会清除打断标记 异常被caych了不会终⽌程序 2、打断正常运⾏的线程, 不会清空打断状态 private static void test2() throws InterruptedException {Thread t2 new Thread(()-{while(true) {Thread current Thread.currentThread();boolean interrupted current.isInterrupted();if(interrupted) {log.debug( 打断状态: {}, interrupted);break;}}}, t2);t2.start();sleep(0.5);t2.interrupt();} 3.打断park的线程 private static void test3() {Thread t1 new Thread(() - {log.debug(park...);LockSupport.park();log.debug(unpark...);log.debug(打断状态{}, Thread.currentThread().isInterrupted());}, t1);t1.start();sleep(0.5);t1.interrupt();
} 打断park线程总结 ● 打断 park 线程, 不会清空打断状态 ● park只会在打断标记为false时⽣效 ● 没有在sleep调⽤时执⾏打断不需要重置打断标记因为这时不会清除打断标记 两阶段终⽌模式 两阶段终⽌模式Two-Phase Termination Pattern 是⼀种⽤于在多线程编程中优雅地停⽌线程的模 式。 该模式的主要思想是在停⽌线程前先通知线程需要停⽌并等待其完成未完成的⼯作然后再真正 地停⽌线程。 在⼀个线程T1中如何优雅的终⽌线程T2这⾥的优雅指的是给T2⼀个料理后事的机会 错误思路 1. 使⽤线程对象的stop⽅法停⽌线程stop⽅法会真正杀死线程如果这时线程锁住了共享资 源那么当它被杀死后就再也没有机会释放锁其他线程将永远⽆法获取锁 2. 使⽤System.exit()⽅法停⽌线程这个⽅法更暴⼒⽬的是停⽌⼀个线程但调⽤这个⽅法会 让整个程序都停⽌ 两阶段终⽌模式的实现 1.利⽤interrupt interrupt 可以打断正在执⾏的线程⽆论这个线程是在 sleepwait还是正常运⾏ 模拟打断
class TPTInterrupt {
private Thread thread;
public void start(){thread new Thread(() - {while(true) {Thread current Thread.currentThread();if(current.isInterrupted()) {log.debug(料理后事);break;}try {Thread.sleep(1000);log.debug(将结果保存);
} catch (InterruptedException e) {current.interrupt();
}
// 执⾏监控操作
}
},监控线程);
thread.start();
}public void stop() {thread.interrupt();
}
} 调⽤ TPTInterrupt t new TPTInterrupt();
t.start();
Thread.sleep(3500);
log.debug(stop);
t.stop(); 输出 主线程启动了t线程后进⼊休眠在这期间t线程⽆法“料理后事”每隔⼀秒将结果保存主线程休眠结束 后调⽤ stop⽅法打断t线程结束 2.利⽤打断标记 设置停⽌标记stop⽤volatile修饰保证其在多线程间的可⻅性 // 停⽌标记⽤ volatile 是为了保证该变量在多个线程之间的可⻅性
// 我们的例⼦中即主线程把它修改为 true 对 t1 线程可⻅
class TPTVolatile {private Thread thread;private volatile boolean stop false;public void start(){thread new Thread(() - {while(true) {Thread current Thread.currentThread();if(stop) {log.debug(料理后事);break;}try {Thread.sleep(1000);log.debug(将结果保存);} catch (InterruptedException e) {}// 执⾏监控操作}},监控线程);thread.start();}public void stop() {stop true;thread.interrupt();}
} 调⽤ TPTVolatile t new TPTVolatile();
t.start();
Thread.sleep(3500);
log.debug(stop);
t.stop(); 输出 不推荐使⽤的⽅法 3.5主线程与守护线程 默认情况下Java 进程需要等待所有线程都运⾏结束才会结束。 有⼀种特殊的线程叫做守护线程只要其它⾮守护线程运⾏结束了即使守护线程的代码没有执⾏ 完也会强制结束。 注意 ● 垃圾回收器线程就是⼀种守护线程 ● Tomcat 中的 Acceptor 和 Poller 线程都是守护线程所以 Tomcat 接收到 shutdown 命令 后不会等待它们处理完当前请求 3.6线程有几种状态 在 Java 中线程共有六种状态 线程在⾃⾝的⽣命周期中 并不是固定地处于某个状态⽽是随着代码的执⾏在不同的状态之间进⾏ 切换 Java 线程状态变化如图⽰ 从操作系统层⾯描述线程有5种状态 【初始状态】仅是在语⾔层⾯创建了线程对象还未与操作系统线程关联 【可运⾏状态】就绪状态指该线程已经被创建与操作系统线程关联可以由 CPU 调度执 ⾏【运⾏状态】指获取了 CPU 时间⽚运⾏中的状态 ● 当 CPU 时间⽚⽤完会从【运⾏状态】转换⾄【可运⾏状态】会导致线程的上下⽂切换 【阻塞状态】 ● 如果调⽤了阻塞 API如 BIO 读写⽂件这时该线程实际不会⽤到 CPU会导致线程上下⽂ 切换进⼊【阻塞状态】 ● 等 BIO 操作完毕会由操作系统唤醒阻塞的线程转换⾄【可运⾏状态】 ● 与【可运⾏状态】的区别是对【阻塞状态】的线程来说只要它们⼀直不唤醒调度器就⼀直 不会考虑调度它们 【终⽌状态】表示线程已经执⾏完毕⽣命周期已经结束不会再转换为其它状态 线程的六种状态 从Java API层⾯描述线程有6种状态 ● NEW是线程创建好但还没运⾏ ● RUNNABLE是运⾏状态 ● BLOCKED是线程阻塞状态 ● WAITING是等待状态 ● TIME_WAITING是超时等待状态 ● TERMINATED线程终⽌状态 线程的六种状态状态⼆哥 线程的状态转换图 今天先更新到这里。