多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

【Java多线程超详细笔记】从0弄懂线程、线程用法、线程生命周期、线程安全

【Java多线程超详细笔记】从0弄懂线程、线程用法、线程生命周期、线程安全 前言平时上课听多线程总是似懂非懂听课能听懂做题写代码就废而且课本内容太杂、太乱课后根本不想翻。所以专门整理这篇极简、干货、复习专用的多线程笔记只记录必须掌握、面试常问、开发常用的内容。以后忘记多线程直接看这一篇就够不用翻书、不用到处找资料。正文一、什么是线程最通俗理解1.1 进程和线程的区别必背进程操作系统资源分配的最小单位。比如微信、浏览器、IDE每一个软件都是一个独立进程进程之间内存相互隔离、互不干扰。线程CPU调度的最小单位属于进程内部的执行单元。一个进程里面可以包含多个线程多个线程共享进程资源堆内存、全局变量。一句话总结进程独立房子线程房子里的打工人多个人同时干活1.2 为什么要用多线程单线程代码从上到下串行执行一个任务卡住后面全部卡住。多线程同一时间执行多个任务。核心作用避免程序阻塞IO、网络请求时不卡死提高 CPU 利用率提高程序运行效率GUI 界面不卡顿二、Java 创建线程的 4 种方式开发必用核心真理所有线程都是调用 start() 启动run() 只是普通方法2.1 方式一继承 Thread 类特点写法简单但是不能再继承其他类实际开发用得少。缺点Java 单继承继承 Thread 后不能再继承其他类。// 继承Thread class MyThread extends Thread { Override public void run() { // 线程执行逻辑 System.out.println(线程运行 Thread.currentThread().getName()); } } public class Test { public static void main(String[] args) { MyThread t new MyThread(); t.setName(线程A); t.start(); // 启动新线程 } }2.2 方式二实现 Runnable 接口最常用解耦、不占用继承名额支持 Lambda 简化日常开发首选。写法1标准写法class MyRunnable implements Runnable { Override public void run() { System.out.println(runnable线程 Thread.currentThread().getName()); } } public class Test { public static void main(String[] args) { Runnable task new MyRunnable(); Thread t new Thread(task); t.start(); } }写法2匿名内部类写法JDK8 之前没有 Lambda 时主流写法不用单独定义一个 MyRunnable 类代码写在创建处public class Test { public static void main(String[] args) { Thread t new Thread(new Runnable() { Override public void run() { System.out.println(匿名内部类线程 Thread.currentThread().getName()); } }); t.start(); } }写法 3Lambda 表达式简化写法JDK8日常开发最常用Runnable 是函数式接口只有一个抽象方法 run可以直接用 Lambda 省略一大堆模板代码格式() - { 线程任务代码 };public class Test { public static void main(String[] args) { Thread t new Thread(() - { System.out.println(Lambda线程 Thread.currentThread().getName()); }); t.start(); } }写法 4任务复用小技巧【高频考点】同一个 Runnable 任务对象可以交给多个 Thread 执行这是 Runnable 对比继承 Thread 最大优势之一共享任务资源class MyRunnable implements Runnable { private int num 10; Override public void run() { while(num 0){ System.out.println(Thread.currentThread().getName() 剩余 num--); } } } public class Test { public static void main(String[] args) { // 同一个任务 Runnable task new MyRunnable(); // 两个线程执行同一个任务 new Thread(task,窗口1).start(); new Thread(task,窗口2).start(); } }2.3 方式三Callable FutureTask带返回值Runnable 没有返回值、不能抛异常Callable 可以获取线程执行结果。适用于需要拿到线程计算结果的场景。import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Test { public static void main(String[] args) throws Exception { CallableInteger call () - { System.out.println(执行任务); return 100; // 返回结果 }; FutureTaskInteger task new FutureTask(call); Thread t new Thread(task); t.start(); Integer res task.get(); // 阻塞等待获取结果 System.out.println(结果 res); } }2.4 方式四线程池生产环境必备频繁创建、销毁线程非常耗性能项目中绝不直接 new Thread全部用线程池。作用线程复用、控制并发数量、防止资源耗尽。import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { public static void main(String[] args) { // 创建固定大小线程池 ExecutorService pool Executors.newFixedThreadPool(3); for (int i 0; i 5; i) { pool.submit(() - { System.out.println(Thread.currentThread().getName() 执行任务); }); } pool.shutdown(); // 关闭线程池 } }三、线程常用核心方法1.start()启动线程向 OS 申请线程资源2.run()线程业务逻辑不要手动调用3.Thread.sleep(long ms)休眠不释放锁抛出 InterruptedException4.wait() / notify() / notifyAll()Object 方法必须在 synchronized 代码块内调用后释放锁进入等待队列5.join()等待另一个线程执行完毕当前线程阻塞Thread t1 new Thread(() - {}); t1.start(); t1.join(); // main线程等t1执行完再继续6.yield() 主动让出 CPU 调度权但不保证一定切换大概率同优先级线程竞争7.interrupt() 中断线程不是强制杀死8.设置中断标记 sleep/wait/join 时被中断抛出异常并清除中断标志 Thread.currentThread() 获取当前执行线程对象四、线程五大状态生命周期新建 NEWnew Thread ()尚未 start就绪 RUNNABLE调用 start ()等待 CPU 调度运行 RUNNINGCPU 选中该线程执行 run阻塞 BLOCKED / WAITING / TIMED_WAITINGBLOCKED等待 synchronized 锁WAITINGwait ()、join () 无限等待TIMED_WAITINGsleep (时间)、wait (超时)终止 TERMINATEDrun 执行完毕或异常退出五、核心难点线程安全多个线程同时操作共享变量会产生数据错乱// 不安全示例 class Ticket implements Runnable { private int num 10; Override public void run() { while(num 0) { System.out.println(出票 num--); } } }解决手段synchronized同步锁方法锁 / 代码块锁Lock显式锁ReentrantLock原子类 AtomicInteger无锁 CAS使用不可变对象、ThreadLocal线程私有数据六、常见坑总结多次调用start()→ 抛出 IllegalThreadStateException把run()当成启动方法没有开启新线程忘记处理 InterruptedException共享变量不加锁出现数据竞争大量新建 Thread不使用线程池造成频繁 GC、资源耗尽死锁多个线程互相持有对方需要的锁七、简单实战示例多窗口卖票加锁保证安全public class TicketDemo { public static void main(String[] args) { Runnable task new Runnable() { int ticket 10; Override public void run() { while(true) { synchronized (this) { // 同步锁 if (ticket 0) break; System.out.println(Thread.currentThread().getName() 卖出第 ticket-- 张票); } } } }; new Thread(task,窗口1).start(); new Thread(task,窗口2).start(); new Thread(task,窗口3).start(); } }
返回列表