14-Java多线程入门:synchronized、volatile与线程通信

📅 2026/7/16 9:29:15
14-Java多线程入门:synchronized、volatile与线程通信
Java多线程入门synchronized、volatile与线程通信开篇面试被问多线程你总是答不到点多线程是Java面试的重灾区。几乎每场面试都会被问到synchronized原理、volatile作用、线程通信、生产者消费者模型。很多人觉得多线程难是因为知识点太散。本文把多线程入门的核心内容串成一条线帮你建立完整知识框架。一、为什么要用多线程程序运行时CPU是最宝贵的资源。单线程程序在遇到IO操作读写文件、网络请求时CPU只能空等。多线程可以让CPU在等待IO时去执行其他任务提高资源利用率。单线程任务A执行 → IO等待 → 继续任务A 多线程任务A执行 → IO等待 → 切换到任务B执行【面试高频】多线程能提高吞吐量但不一定缩短单个任务的执行时间。二、线程创建的三种方式2.1 继承 Thread 类classMyThreadextendsThread{Overridepublicvoidrun(){System.out.println(线程执行Thread.currentThread().getName());}}MyThreadt1newMyThread();t1.start();2.2 实现 Runnable 接口推荐classMyRunnableimplementsRunnable{Overridepublicvoidrun(){System.out.println(线程执行Thread.currentThread().getName());}}Threadt2newThread(newMyRunnable());t2.start();2.3 实现 Callable 接口有返回值classMyCallableimplementsCallableString{OverridepublicStringcall()throwsException{return任务结果;}}FutureTaskStringfutureTasknewFutureTask(newMyCallable());newThread(futureTask).start();StringresultfutureTask.get();// 阻塞获取结果【面试高频】推荐用Runnable或Callable因为Java是单继承继承Thread会限制扩展性。三、线程的6种状态NEW新建 → RUNNABLE可运行 → RUNNING运行中 → BLOCKED阻塞 → WAITING等待 → TIMED_WAITING超时等待 → TERMINATED终止状态说明NEW线程刚创建未调用startRUNNABLE等待CPU调度BLOCKED等待锁WAITING无限等待需要被唤醒TIMED_WAITING限时等待TERMINATED执行完毕【面试陷阱】线程调用start()后不是立刻执行而是进入RUNNABLE状态等待CPU调度。四、线程安全问题4.1 问题的根源多个线程同时读写同一个共享变量就可能出现数据不一致。例如publicclassCounter{privateintcount0;publicvoidincrement(){count;// 不是原子操作}}两个线程各执行100万次结果通常小于200万。4.2 线程安全三条件多线程环境存在共享资源对共享资源有写操作只要破坏其中一个条件就能避免线程安全问题。五、synchronized关键字5.1 三种用法// 1. 同步代码块synchronized(obj){// 临界区}// 2. 同步实例方法锁对象是 thispublicsynchronizedvoidmethod(){// 临界区}// 3. 同步静态方法锁对象是 Class 对象publicstaticsynchronizedvoidmethod(){// 临界区}5.2 锁对象必须相同多个线程只有竞争同一把锁synchronized才有意义。// 错误每次new新对象当锁synchronized(newObject()){count;}5.3 一个完整示例publicclassSafeCounter{privateintcount0;publicsynchronizedvoidincrement(){count;}publicsynchronizedintgetCount(){returncount;}}【面试高频】synchronized在JDK6之后做了大量优化包括偏向锁、轻量级锁、重量级锁性能已经很好。六、volatile关键字6.1 volatile的作用volatile有两个作用保证可见性一个线程修改了变量其他线程能立即看到。禁止指令重排序防止编译器和CPU优化执行顺序。6.2 volatile不能保证原子性publicclassVolatileDemo{privatevolatileintcount0;publicvoidincrement(){count;// 仍然不是线程安全的}}count包含读取、修改、写回三个步骤。volatile只能保证最后一步写回后被其他线程看到但不能保证中间不被打断。【面试陷阱】volatile适用于状态标志位不适用于计数器。6.3 volatile正确用法publicclassFlagDemo{privatevolatilebooleanrunningtrue;publicvoidstop(){runningfalse;}publicvoiddoWork(){while(running){// 执行任务}}}七、线程通信7.1 wait / notify 基础wait()让当前线程等待并释放锁。notify()唤醒一个等待的线程。notifyAll()唤醒所有等待的线程。这三个方法必须在synchronized代码块中调用并且调用对象是锁对象。7.2 经典案例生产者消费者一个线程生产数据一个线程消费数据通过wait/notify协调。publicclassBuffer{privateintdata;privatebooleanemptytrue;publicsynchronizedvoidproduce(intvalue)throwsInterruptedException{while(!empty){wait();// 缓冲区有数据等待消费}datavalue;emptyfalse;notifyAll();// 唤醒消费者}publicsynchronizedintconsume()throwsInterruptedException{while(empty){wait();// 缓冲区为空等待生产}emptytrue;notifyAll();// 唤醒生产者returndata;}}【面试高频】为什么用while而不是if因为被唤醒后条件可能又不满足了while会再次判断。八、与Spring框架的连接Spring的Async注解底层就是线程池ServicepublicclassOrderService{Async(taskExecutor)publicvoidsendSms(Stringphone){// 异步发送短信}}配置线程池Bean(taskExecutor)publicExecutortaskExecutor(){ThreadPoolTaskExecutorexecutornewThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(20);executor.setQueueCapacity(100);executor.initialize();returnexecutor;}【面试高频】实际开发中很少直接new Thread()而是用线程池管理线程。九、开发踩坑实录坑 1调用 run() 而不是 start()ThreadtnewThread(newMyRunnable());t.run();// 普通方法调用没有启动新线程后果代码在当前线程同步执行没有多线程效果。正确做法调用t.start()。坑 2synchronized锁错对象publicsynchronizedvoidmethod(){// 锁是 thiscount;}如果两个线程传入的是不同实例synchronized就锁不住了。正确做法共享变量加static或者锁同一个全局对象。坑 3在锁里做耗时操作synchronized(lock){Thread.sleep(10000);// 持有锁10秒}后果其他线程一直阻塞性能极差。正确做法锁里只放必要操作IO和耗时计算放到锁外。坑 4wait没有放在循环里synchronized(lock){if(condition){lock.wait();}}后果被虚假唤醒后条件可能又不满足了程序会出错。正确做法用while循环判断条件。十、面试速记卡10.1 核心概念速查概念一句话解释线程CPU调度的基本单位synchronized互斥锁保证同一时刻只有一个线程执行volatile保证可见性、禁止重排序不保证原子性wait释放锁并等待notify唤醒一个等待线程notifyAll唤醒所有等待线程线程池管理和复用线程减少创建销毁开销10.2 高频面试题线程创建的三种方式是什么推荐哪种start()和run()有什么区别synchronized的锁对象分别是什么volatile能保证线程安全吗为什么sleep()和wait()有什么区别为什么wait()要放在循环里什么是虚假唤醒生产者消费者模型怎么实现10.3 口诀总结线程创建三方式Runable更推荐 start启动才有效run只是普通调 synchronized上把锁对象相同才有效 volatile可见不原子状态标志最适合 wait会释放锁notify要唤醒 while判断条件稳虚假唤醒也不怕十一、小结多线程入门抓住三个重点线程如何创建、线程安全问题如何解决、线程之间如何通信。synchronized解决互斥volatile解决可见性wait/notify解决协作。掌握这三个面试中的基础多线程问题基本都能应对。实际开发中尽量少直接操作线程优先使用线程池和框架提供的异步能力。