并发,并行,串行
- 并发:宏观上同时进行,微观上交替切换执行,单个 CPU 核心,靠快速切换任务
- 并行:同一时刻,多个任务真正同时运行,多个 CPU 核心,每个核心独立跑一个任务
- 串行:同一时刻只做一件事,做完一件再做下一件
线程
什么是线程
一个程序运行时,内部可以分成一个或多个线程,线程是CPU 执行任务的最小单位
创建线程的方式
实现 Runnable
classMyRunnableimplementsRunnable{@Overridepublicvoidrun(){System.out.println("Runnable 线程:"+Thread.currentThread().getName());}}publicclassTest{publicstaticvoidmain(String[]args){MyRunnabler=newMyRunnable();Threadt=newThread(r);t.start();}}继承 Thread
classMyThreadextendsThread{@Overridepublicvoidrun(){System.out.println("线程运行:"+Thread.currentThread().getName());}}publicclassTest{publicstaticvoidmain(String[]args){MyThreadt=newMyThread();t.start();// 启动线程}}实现 Callable
classMyCallableimplementsCallable<Integer>{@OverridepublicIntegercall()throwsException{System.out.println("Callable 线程运行");return100;// 可以返回结果}}publicclassTest{publicstaticvoidmain(String[]args)throwsException{FutureTask<Integer>task=newFutureTask<>(newMyCallable());Threadt=newThread(task);t.start();// 获取返回值Integerres=task.get();System.out.println("结果:"+res);}}线程池创建
publicclassTest{publicstaticvoidmain(String[]args){// 创建线程池ExecutorServicepool=Executors.newFixedThreadPool(3);// 提交任务pool.submit(()->{System.out.println("线程池线程:"+Thread.currentThread().getName());});pool.shutdown();// 关闭线程池}}线程的生命周期
- 新建,创建了一个线程对象,但是没有调用 start() 方法
- 就绪,调用了 start(),等着 CPU 分配时间片,一旦拿到 CPU,就开始执行 run()
- 阻塞,线程阻塞于锁
- 等待,主动无限期等别人唤醒,例如:wait() / join() / LockSupport.park()
- 定时等待,有时间限制的等待,例如:sleep(1000) / wait(1000) / join(1000)
- 终止,run() 执行完 / 异常退出
状态的流转关系
NEW → start() → RUNNABLE RUNNABLE ↓ 抢锁失败 BLOCKED → 拿到锁 → RUNNABLE RUNNABLE ↓ wait()/join() WAITING → notify()/notifyAll() → RUNNABLE RUNNABLE ↓ sleep(1000) TIMED_WAITING → 时间到 → RUNNABLE RUNNABLE → 执行完毕 → TERMINATED如何终止线程
stop
强制杀死线程,已弃用
interrupt
publicclassTest{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadt=newThread(()->{try{while(!Thread.currentThread().isInterrupted()){System.out.println("运行中...");Thread.sleep(1000);}}catch(InterruptedExceptione){// 收到中断信号,退出System.out.println("线程被中断终止");}});t.start();Thread.sleep(2000);t.interrupt();// 中断}}interrupt()、interrupted()、isInterrupted()的作用以及区别
thread.interrupt()
作用:中断线程(设置 interupted = true)
类型:实例方法
效果:
如果线程正在运行:仅设置中断标志,不停止运行
如果线程在 sleep()/wait()/join():会抛出 InterruptedException,并清除中断标志
不会强制停止线程,只是通知thread.isInterrupted()
作用:查询是否被中断
类型:实例方法
特点:
只读取,不改变状态
调用一次,标记依然保留Thread.interrupted()
作用:查询 + 清除中断标记
类型:静态方法
特点:
第一次调用返回 true
第二次调用一定返回 false(因为复位了)只针对当前执行的线程,例如在 main 线程中执行 t1.interrupted(),其实查询的是 main 线程的 interupted 变量并复位
synchronized
锁升级
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
单向升级,不可降级,只有释放锁后才会变回无锁
无锁
默认的无锁状态
偏向锁(jdk 15+ 默认取消偏向锁)
锁一直被同一个线程拿
第一次抢锁:CAS 把当前线程 ID 写入对象头,之后再来,只要线程 ID 匹配,直接进,不需要 CAS
第二个线程来竞争时,偏向锁被撤销 → 升级轻量级锁
轻量级锁
两个线程交替用锁,无长时间阻塞
参考
线程在自己的栈帧创建 Lock Record,把对象头复制到 Lock Record 中,通过 CAS 把对象头换成指向自己 Lock Record 的指针
成功:拿到锁
失败:将锁升级为重量级锁,然后进入 cxq 链表进行自旋,自旋成功则获取锁,自旋多次失败则会进入阻塞状态,等待唤醒
重量级锁
monitor-enter
每一个对象都会和一个监视器monitor关联, 监视器被占用时会被锁住, 其他线程无法来获取该监视器, 当JVM执行某个对象的 monitorenter(synchronized(obj)) 方法时, 会尝试去获取对象的监视器所有权, 过程如下:
- 如果monitor 的进入数为 0, 则线程可以进入monitor, 并将 monitor 的进入数变为 1, 当前线程成为 monitor 的所有者
- 如果线程已经拥有了 monitor 的所有权, 允许重入 monitor, 此时 monitor 的进入数 +1
- 如果其他线程已经占有了 monitor, 当前线程尝试获取 monitor 所有权时会被阻塞, 直到 monitor 的进入数为 0
monitor-exit
- 能执行monitorexit指令的线程一定是拥有monitor所有权的线程
- 执行monitorexit指令后, monitor 的进入数 -1, 当进入数变为 0 后, 表示当前线程释放锁, 不再拥有 monitor 的所有权, 此时其他阻塞的线程可以去尝试获取monitor的所有权
synchronized 执行时, 没有竞争到锁的线程会被挂起, 此时需要调用操作系统的park() 方法, 竞争到锁的线程会被 unpark() 唤醒