你好我是fengxin_rou这是我的个人主页fengxin_rou的主页❄️欢迎查看我的专栏我的专栏《Java后端学习》、《JAVASE基础》、《JUC并发》、《redis》、《JVM虚拟机》、《MYSQL》、《黑马点评》、《rabbitmq》、《JavaWebAI的talis学习系统》、《苍穹外卖》目录前言一、.进程和线程的区别1.基础定义2.核心7大区别二、Go的协程和Java线程的区别1.首先最核心的区别是它们的调度模型不一样。2.从资源消耗上来说差别非常明显3.调度方式上也不太一样。4.从使用方式上来讲Go的协程用起来要简单太多了。5.通信机制也有很大差异。6.性能上来说因为Go协程的轻量级特性和高效的调度器在高并发场景下Go的表现通常会更好。三、线程有几种创建方式各自优缺点1.继承Thread类2.实现Runnable接口3.实现Callable接口与FutureTask4.使用线程池补系统吞吐量是指单位时间内系统成功处理的任务或业务量四、线程的五大生命周期状态及流转过程五、什么是守护线程Daemon Thread和用户线程区别应用场景六、sleep ()、wait ()、yield ()、join () 区别前言本栏目将讲解线程相关的知识这一篇主要讲解一下线程基础一、.进程和线程的区别1.基础定义进程操作系统资源分配的最小单位独立执行程序线程cpu调度执行最小单位进程里的执行支路2.核心7大区别资源占用进程独占内存、文件、cpu、端口、资源独立线程共享进程所有的全部资源自身存少量栈、寄存器地址空间进程各自独立虚拟地址互不访问线程同进程内所有线程共享一片地址空间创建销毁开销进程开销巨大耗时久线程轻量级创建切换成本极低通信方式进程只靠消息队列、管道、Socket、共享内存交互线程直接读写全局变量成员变量即可通信崩溃影响进程崩溃仅自身终止其他线程不受影响线程崩溃整个所属线程直接退出从属关系进程相互独立线程依附进程不能单独存在一个进程最少自带一个主线程使用场景进程多软件隔离运行、服务隔离部署线程单程序内部并发任务、异步处理二、Go的协程和Java线程的区别1.首先最核心的区别是它们的调度模型不一样。Java线程一个线程对应着一个操作系统用户线程线程的创建销毁调度都是经过操作系统内核的go的协程也就是goroutine是用户及的轻量级线程是Go运行时自己调度的不需要经过操作系统内核一个用户态调度器对应着多个goroutine2.从资源消耗上来说差别非常明显Java线程的创建销毁开销很大一个线程差不多1MB每一个线程都要走操作系统所以说需要线程池里复用线程Go协程就比较轻量一个goroutine栈队列可以无限拉长并且一个协程 大小也就2KB所以资源消耗比较小3.调度方式上也不太一样。Java线程是抢占式调度所有的线程切换都是由操作系统决定的这里就涉及到了用户态和内核态的切换导致开销比较大Go协程的调度是用户态决定的在调度时有一个GMP模型G是goroutineM是用户态P是逻辑处理器Go会把多个写成映射到少量的操作系统上执行切换完全是在用户态上完成这样就导致切换成本十分低。而且go的调度器在协程阻塞时会把这个协程挂起执行其他协程十分高效4.从使用方式上来讲Go的协程用起来要简单太多了。在go里面只需要在想要创建的协程前加上一个go关键字就可以了package main import ( fmt time ) func doSomething(name string) { for i : 0; i 3; i { fmt.Printf(%s: %d\n, name, i) time.Sleep(100 * time.Millisecond) } } func main() { // 创建协程就这么简单, 加个go关键字 go doSomething(协程1) go doSomething(协程2) go doSomething(协程3) // 等待协程执行完 time.Sleep(time.Second) fmt.Println(主程序结束) }在java里需要new 线程对象实现Runnable接口或者用线程池代码量比较复杂public class RunnableExample { public static void main(String[] args) { // 实现Runnable接口 Runnable task new Runnable() { Override public void run() { for (int i 0; i 3; i) { System.out.println(Thread.currentThread().getName() : i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }; // 创建Thread对象并传入Runnable Thread thread1 new Thread(task, 线程1); Thread thread2 new Thread(task, 线程2); Thread thread3 new Thread(task, 线程3); thread1.start(); thread2.start(); thread3.start(); } }5.通信机制也有很大差异。Java通信需要用共享内存来通信这就要用synchornized、lock这些锁来完善同步还要避免死锁问题比较麻烦go通信虽然也可以使用共享内存但更推荐使用channel来通信代码会清晰一点也不容易出现并发问题6.性能上来说因为Go协程的轻量级特性和高效的调度器在高并发场景下Go的表现通常会更好。如果要同时处理几万个请求Java需要用复杂的异步框架锁处理机制而go只需要为每一个请求开一个协程就行了代码简单性能还好。但是在传统企业级项目有很多框架和中间件的使用这里使用java还是比较好三、线程有几种创建方式各自优缺点1.继承Thread类优点继承Thread操作简单用Thread.currentThread()可以得到当前运行的线程用this也可以直接得到当前运行的线程缺点继承Thread类后不能继承其他类2.实现Runnable接口若是一个方法继承了其他父类就可以实现java.lang.Runnable接口实现接口需要重写它的Run方法然后在Thread构造器里把Runnable对象作为参数传递进去然后使用strat方法创建线程优点可以在继承其他父类时创建线程并且可以实现多个线程资源共享可以操作同一个Runnable对象可以把cpu代码和数据分开更好体现面向对象的编程思想缺点操作比较复杂访问当前线程只能用Tread.currentTread()方法3.实现Callable接口与FutureTaskjava.util.concurrent.Callable接口类似于Runnable接口但Callabe接口的call方法有返回值还可以抛出异常想要创建线程需要先创建Callable对象然后把对象作为参数传给Future Task任务管理器然后创建线程执行Callable任务、还可以得到任务结果优点可以继承其他父类并且实现资源共享缺点操作复杂并且访问当前线程只能Thread.currentThead()4.使用线程池Java 5 引入的java.util.concurrent.ExecutorService接口和其他类提供了线程池的支持避免了查询和创建线程可以通过Executor的静态方法创建不同类型的线程池、提高了线程管理效率优点:可以重用预先创建的线程避免了创建和查询线程的开销提高了线程管理的效率。线程池可以合理的分配线程避免资源浪费如果遇到并发情况线程池还可以快速提供线程来处理任务减少等待时间。合理设置线程池的大小可以提高cpu的利用率和系统吞吐量补系统吞吐量是指单位时间内系统成功处理的任务或业务量缺点代码操作比较复杂在设置线程池参数的时候如果出错可能会引起死锁资源进程等问题修复起来比较复杂四、线程的五大生命周期状态及流转过程五大生命周期new(新的)、runnable就绪、running运行、blocked(阻塞)、Terminated终止线程状态解释NEW尚未启动的线程状态即线程创建还未调用start方法RUNNABLE就绪状态调用start等待调度正在运行BLOCKED等待监视器锁时陷入阻塞状态WAITING等待状态的线程正在等待另一线程执行特定的操作如notifyTIMED_WAITING具有指定等待时间的等待状态TERMINATED线程完成执行终止状态流转过程就是新建之后进入就绪状态然后争夺锁争夺锁失败之后进入阻塞锁释放了之后就进入就绪状态再次争夺直到拿到锁五、什么是守护线程Daemon Thread和用户线程区别应用场景定义守护线程是后台服务线程当所有的普通线程(用户线程)停止了JVM退出守护线程也强制退出区别1.退出时机普通线程是执行完后jvm才结束退出。守护线程则是jvm退出后不管是否执行完都退出2.创建设置线程创建默认就是用户线程而守护线程需要thread.setDaemon(true)才行且必须在调用start()之前使用否则报错3.生命周期用户线程独立生命周期不受其他线程影响。而守护线程的生命周期依附于所有的用户线程应用场景用于后台监控、心跳、定时巡检、垃圾回收、日志这些场景比如监听是否全部普通线程都还在执行监控垃圾回收是否执行完成。六、sleep ()、wait ()、yield ()、join () 区别对比维度sleep()wait()yield()join()所属类Thread静态Object实例Thread静态Thread实例释放锁❌✅❌✅底层依赖 wait使用位置任意位置仅同步块内任意位置任意位置状态变化TIMED_WAITINGWAITING/TIMED_WAITING仍为 RUNNABLEWAITING/TIMED_WAITING恢复方式超时自动恢复notify / 超时立即重新竞争 CPU目标线程执行完毕类别sleep是Thread类 的静态方法在任何地方都可以通过Thread对象来调用而wait方法是Object类的实例方法意思是需要实例对象来调用锁释放sleep不会让线程释放锁会一直持有锁wait会释放锁让其他线程来获取锁使用前提sleep在任何时候都能调用而wait只有在同步块也就是sychronized代码块里才能使用使用后会释放锁唤醒机制sleep虽是超时等待但不涉及线程协作所以notify不能唤醒只能自动恢复而wait必须要用notify或notifyAll或超时唤醒补notify只能唤醒调用同一个对象池中等待的线程而sleep不会进入对象的线程池(因为不释放锁)所以不会被唤醒释放锁总结 释放锁wait()、join()不释放锁sleep()、yield()同步块限制 只有wait()强制要求在synchronized中使用其余三者无限制。只有wait()依赖notify()/notifyAll()唤醒sleep/yield/join都不受notify影响。七、为什么 wait、notify 要在 synchronized 里执行wait()需要释放锁所以需要拿到锁才能执行notify1.唤醒操作需要操作锁对象监视器而操作锁对象监视器需要锁2.保证唤醒修改条件的原子性防止出现修改条件后没有线程在等待的局面八、什么是线程上下文切换为什么会耗时间线程上下文切换CPU暂停当前线程并且保存当前线程的运行状态(上下文)切换到另一个线程这个过程就是上下文切换上下文一个线程执行时的寄存器数据程序计数器、栈信息、程序标记等信息是恢复线程执行的全部数据耗时原因1.保存旧上下文和加载新上下文需要时间2.线程切换需要从用户态转到内核态需要时间3.cpu高速缓存失效再次加载线程需要重新缓存进入cpu4.操作系统的调度下一个线程的算法也需要开销