【Java并发基础】多线程核心知识详解(线程及创建、生命周期、线程中断机制,线程安全问题)
本文整理了多线程的核心知识从线程概念、创建方式、生命周期、常用方法到线程安全与JMM内存模型。一、多线程基础①进程Process是操作系统分配资源的基本单位比如打开一个浏览器就是一个进程。---高隔离性互不影响每个进程都有·独立的内存空间·独立的资源文件、句柄等不同进程·不能直接访问彼此内存·需要通过 IPC进程间通信如管道消息队列Socket... ...② 线程Thread是程序执行的最小单位一个进程中可以有多个线程这些线程共享进程的资源。---高性能并发任务间共享数据线程之间共享内存堆全局变量文件资源但每个线程也有自己的栈空间程序计数器单线程进程只有一个执行流多线程进程多个线程同时执行任务浏览器进程里可能有一个线程负责UI一个线程负责网络请求... ...进程vs线程对比项进程线程定义资源分配单位执行单位内存独立共享所属的进程创建开销大小切换开销大小通信方式IPC复杂直接共享简单但危险崩溃影响不影响其他进程可能导致整个进程崩溃Q:为什么需要多线程① 提高 CPU 利用率单线程无法充分利用多核 CPU多线程可以并发执行。并发Concurrency多任务“轮流执行”单核 CPU 也可以实现任务A → 任务B → 任务A → 任务B → …本质快速切换并行Parallelism多任务“同时执行”需要多核 CPU核心1任务A核心2任务B本质真正同时执行② 提高程序效率并发IO等待时可以执行其他任务减少整体执行时间③ 线程比进程更轻量创建更快切换更快调度开销更小二、线程的创建方法1. 继承 Threadclass MyThread extends Thread { Override public void run() { System.out.println(线程执行); } } // 启动 new MyThread().start();2. 实现 Runnable推荐避免继承限制更灵活使用 Runnable 可以避免 Java 单继承的限制单继承就是class A extends B, C {} // 不允许class MyThread extends Thread这个类已经继承了Thread就不能再继承别的类了class MyRunnable implements Runnable { Override public void run() { System.out.println(线程执行); } } Thread t new Thread(new MyRunnable()); t.start();3. Lambda 写法常用可以简化代码Lambda 是一种“简化匿名函数”的写法把“方法”当成参数传。Lambda 的基本语法(参数) - { 方法体 }new Thread(() - { System.out.println(线程执行); }).start();三、Thread 常用方法start()t.start();启动线程真正创建线程sleep()Thread.sleep(1000);让线程休眠1000ms不会释放锁join():t.join()所在的主线程等待t线程执行完interrupt():t.interrupt()中断线程协作机制不是强制停止currentThread():Thread.currentThread();获取当前线程四、线程的生命周期重点线程状态Thread.StateNEW新建RUNNABLE可运行BLOCKED阻塞锁竞争WAITING等待TIMED_WAITING超时等待TERMINATED结束public class ThreadStateDemo { private static final Object lock new Object(); public static void main(String[] args) throws Exception { Thread t1 new Thread(() - { try { // RUNNABLE synchronized (lock) { System.out.println(t1 获取到锁); // TIMED_WAITINGsleep Thread.sleep(1000); // WAITINGwait System.out.println(t1 进入等待); lock.wait(); // 被唤醒 → RUNNABLE System.out.println(t1 被唤醒继续执行); } } catch (InterruptedException e) { e.printStackTrace(); } }); // NEW 状态 System.out.println(t1 state: t1.getState()); // 启动线程 → RUNNABLE t1.start(); Thread.sleep(100); // 确保 t1 先拿到锁 Thread t2 new Thread(() - { synchronized (lock) { System.out.println(t2 获取到锁); // 唤醒 WAITING 的线程 lock.notify(); // TIMED_WAITINGsleep try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }); // t2 启动 t2.start(); // main 线程等待 t1 执行完 → WAITINGjoin t1.join(); // TERMINATED System.out.println(t1 执行结束状态 t1.getState()); } }状态转换涉及的方法调用关系方法用法调用者作用对象是否释放锁状态sleep让当前线程暂停一段时间模拟耗时Thread静态方法线程类不释放TIMED_WAITINGwait让当前线程释放锁obj并等待其他线程的通知Object obj对象锁释放WAITINGnotify随机唤醒一个在锁对象obj上等待的线程Object obj对象锁---唤醒线程join让主线程等待当前线程th执行完毕Thread th线程对象间接WAITINGyield让当前线程主动让出 CPU让其他线程有机会执行不保证生效Thread静态方法当前线程不释放RUNNABLE五、线程中断机制[中断标志位 true] ≠ [线程已经停止]它只是一个“请求线程停止”的信号,因为强制杀死线程是不安全的可能会导致数据不一致锁没释放资源泄漏//interrupt(),给线程发中断信号,设置中断标志位为true。线程是否停止取决自己是否处理这个信号 //线程在正常运行--调用 interrupt 后不会立刻停,只是标志位变为 true //线程正在sleep--如果被 interrupt,会提前结束休眠 //线程在阻塞状态Thread.sleep()、wait()、join()--调用 interrupt 后,会立刻抛异常,中断标志位会被清除变回 false是否停止取决于怎么处理异常 thread.interrupt(); //isInterrupted()获取当前线程的中断状态true / false,不清除只是“查看” Thread.currentThread().isInterrupted(); //interrupted()获取当前线程的中断状态,会清除中断标志(把中断状态从 true 改回 false) Thread.interrupted();六、线程安全问题核心重点线程安全多线程执行结果仍然正确为什么会不安全共享数据多个线程访问同一变量执行顺序不可控线程调度是随机的操作不是原子性 count实际上是三步读取修改写回多线程下可能出错//共享数据问题多个线程访问同一变量 class SharedDataDemo { static int count 0; public static void main(String[] args) { Runnable task () - { for (int i 0; i 1000; i) { count; // 多线程共享变量 } }; Thread t1 new Thread(task); Thread t2 new Thread(task); t1.start(); t2.start(); } } /* count 被多个线程共享 两个线程同时修改它 预期2000实际可能小于 2000 因为线程“同时写”数据被覆盖 */ //执行顺序不可控线程调度随机 public class OrderDemo { public static void main(String[] args) { Thread t1 new Thread(() - { System.out.println(线程1); }); Thread t2 new Thread(() - { System.out.println(线程2); }); t1.start(); t2.start(); } } /* 可能是线程1线程2 也可能是线程2线程1 */ //非原子操作count问题 class AtomicDemo { static int count 0; public static void main(String[] args) throws Exception { Thread t1 new Thread(() - { count; }); Thread t2 new Thread(() - { count; }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count count); } } /* 多个线程同时读取同一初始值并各自修改后写回导致后一次写覆盖前一次结果从而出现数据丢失。 */Java 内存模型主内存 → 线程工作内存 → 修改 → 写回主内存问题来源:① 可见性问题一个线程修改另一个线程看不到② 原子性问题操作被拆分③ 有序性问题指令顺序可能改变指令重排序编译器 / CPU 会调整代码执行顺序提高性能int a 0;int b 0;a 1;b a;→实际执行顺序可能变成b a;a 1;