公司动态
Java多线程初阶完整总结|线程、锁、volatile、等待通知、常见案例
一、认识线程1. 什么是线程线程就是一条独立执行流多个线程可以同时执行多份代码。 进程是资源分配的最小单位线程是CPU调度的最小单位。一个进程至少包含1个主线程同一个进程内多个线程共享堆、方法区内存每个线程私有虚拟机栈、本地方法栈、程序计数器进程之间内存相互隔离一个线程崩溃会导致整个进程崩溃。为什么用线程充分利用多核CPU算力IO等待时可以执行其他任务提升程序整体效率相比进程线程创建、销毁、调度开销更小。2. Java创建线程的4种写法继承Thread类重写run()调用start()启动线程class MyThread extends Thread { Override public void run() { System.out.println(线程执行); } } // 使用 MyThread t new MyThread(); t.start();实现Runnable接口传入Thread构造器class MyRunnable implements Runnable{ Override public void run() { System.out.println(runnable执行); } } Thread t new Thread(new MyRunnable()); t.start();匿名内部类ThreadThread t new Thread(){ Override public void run() {} };Lambda表达式推荐代码简洁Thread t new Thread(() - { System.out.println(lambda线程); }); t.start();⚠️注意run()只是普通方法start()才会向操作系统申请创建真正的线程。3. Thread常用属性与方法API说明getId()线程唯一idgetName()获取线程名字getState()获取线程状态getPriority()获取优先级优先级高只是更容易被调度不代表一定先执行isDaemon()是否后台守护线程JVM等待所有非后台线程结束才退出isAlive()线程是否存活不是NEW、不是TERMINATED即为存活interrupt()中断线程设置中断标记如果线程阻塞(sleep/wait)会抛出InterruptedException并清除标记currentThread()静态方法获取当前正在执行的线程对象sleep(ms)休眠线程不会释放锁实际休眠时间≥设置时间受操作系统调度影响join()等待其他线程执行完毕支持设置最大等待时间中断线程两种方式自定义volatile布尔标记位循环判断标记退出interrupt()isInterrupted()/Thread.interrupted()。interrupted()读取中断标记同时清除标记isInterrupted()只读取不清除标记。二、线程的6种状态与流转线程状态是枚举Thread.State一共6种NEW线程对象创建还没调用start()RUNNABLE就绪/运行中代表线程可以被CPU调度执行不一定正在跑CPUBLOCKED等待获取synchronized锁锁被别的线程占有阻塞排队WAITING无限等待等待其他线程唤醒wait()、join()、park()TIMED_WAITING限时等待超时自动唤醒sleep()、wait(time)TERMINATED线程执行完毕区分BLOCKED抢锁失败等锁WAITING / TIMED_WAITING主动放弃CPU等待其他线程通知或者超时。三、线程安全定义多线程环境下程序运行结果和单线程预期结果一致就是线程安全。线程不安全的4个根源线程抢占式随机调度操作系统随时切换线程执行顺序不可预测修改共享变量多个线程同时读写同一个内存变量不具备原子性一条Java代码可能对应多条CPU指令执行中途会被打断。 例如count分为三步读内存 → CPU计算 → 写回内存多线程并发会出现数据丢失内存可见性问题JMM Java内存模型线程拥有自己的工作内存CPU缓存/寄存器修改先写副本不一定立刻刷入主内存其他线程看不到最新值指令重排序编译器、CPU为性能对指令进行重排单线程逻辑不变多线程下会出现异常。Java内存模型 JMM主内存真正物理内存存放共享变量工作内存每个线程私有对应CPU寄存器、高速缓存是变量副本。 线程读写共享变量都是操作工作内存再同步主内存会带来可见性bug。四、synchronized 监视器锁synchronized是Java内置的悲观锁基于操作系统mutex实现。三大特性互斥原子性同一时间只允许一个线程进入同步块。进入加锁退出解锁竞争失败线程进入阻塞队列BLOCKED。可重入同一个线程可以重复获取同一把锁内部维护锁持有者计数器计数器归0才释放锁避免自己把自己锁死。保证可见性、禁止指令重排序。四种写法锁普通对象代码块synchronized(对象){}锁当前对象thissynchronized(this){}修饰普通方法锁当前实例对象public synchronized void test(){}修饰静态方法锁类对象.classpublic static synchronized void test(){}关键点只有竞争同一对象锁才会阻塞不同对象互不干扰。JDK线程安全/不安全类❌线程不安全ArrayList、HashMap、HashSet、StringBuilder✅线程安全StringBuffer、Vector、HashTable不推荐、ConcurrentHashMapString不可变天生线程安全。五、volatile关键字作用保证变量内存可见性禁止指令重排序不保证原子性。写volatile变量修改工作内存副本强制刷新到主内存读volatile变量强制从主内存读取最新值不使用缓存副本。注意volatile int count不能解决count线程不安全因为不是原子操作。使用场景多线程下一个线程写、多个线程读的标记变量。六、wait / notify / notifyAll属于Object的方法用来做线程之间的协作通信控制线程执行顺序。wait()当前线程释放锁进入对象等待队列等待被唤醒、超时、被interrupt必须写在synchronized同步块内部否则抛异常。notify()唤醒该对象等待队列中随机一个线程不会立刻释放锁退出同步代码块之后锁才释放。notifyAll()唤醒该对象等待队列全部等待线程唤醒后所有线程需要重新竞争锁。wait vs sleep对比waitsleep归属Object方法Thread静态方法锁必须在synchronized内调用调用后释放锁不需要锁休眠不释放锁用途线程间通信协作单纯让线程休眠一段时间最佳实践wait()要写在while循环里面防止虚假唤醒。七、多线程经典案例案例1单例模式饿汉模式类加载直接创建实例天然线程安全class Singleton{ private static Singleton instance new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }懒汉模式双重检查锁DCLclass Singleton{ // volatile禁止指令重排序防止半初始化对象 private static volatile Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance null){ //外层判断减少锁竞争 synchronized (Singleton.class){ if(instance null){ instance new Singleton(); } } } return instance; } }volatile作用阻止new Singleton()指令重排避免其他线程拿到未初始化完成的对象。案例2阻塞队列 生产者消费者模型阻塞队列特性队列满时入队put()阻塞队列空时出队take()阻塞。生产者‑消费者模型作用解耦生产者、消费者不直接交互通过队列通信削峰填谷缓冲流量平衡生产和消费速度防止瞬时流量打垮服务。标准实现LinkedBlockingQueueput()入队take()阻塞取出。简易实现思路数组存储元素synchronizedwait/notifyAllput满就waittake空就wait。案例3定时器TimerJDK提供Timerschedule(task,delay)延迟执行任务。简易定时器实现优先队列存放定时任务后台线程循环扫描队首任务时间到执行任务。案例4线程池频繁创建销毁线程开销高线程池复用已有线程。 核心组成核心线程数、最大线程数、阻塞队列、空闲存活时间、拒绝策略。Executors工具快速创建线程池newFixedThreadPool(n)固定线程数newCachedThreadPool()动态扩容newSingleThreadExecutor()单线程池四种拒绝策略AbortPolicy直接抛异常默认CallerRunsPolicy调用者线程自己执行任务DiscardOldestPolicy丢弃队列最老任务DiscardPolicy直接丢弃新任务不抛异常八、保证线程安全的整体思路不使用共享变量共享变量只读使用不可变对象修改共享变量时保证原子性、可见性、有序性原子性synchronized、锁、CAS可见性volatile、锁有序性volatile、锁禁止重排序。结尾小结多线程核心难点在于理解抢占调度带来执行顺序不可控引出原子性、可见性、重排序三类问题。synchronized悲观锁保证原子可见有序会阻塞volatile只解决可见性与重排序不能保证原子性wait‑notify做线程通信业务优先使用线程池不要手动频繁new Thread。