网站开发交流群,wordpress 36氪,云服务器可以做视频网站吗,做电商一般月入多少钱一#xff0c;并发锁 1、锁是什么#xff1f; 锁是一种同步机制#xff0c;用于在多线程#xff08;或多进程#xff09;环境中#xff0c;控制对共享资源的访问。 ⭕共享资源#xff1a;好比一个单人使用的公共卫生间 ⭕锁#xff1a;相当于卫生间的门锁机制 ⭕线程…一并发锁1、锁是什么锁是一种同步机制用于在多线程或多进程环境中控制对共享资源的访问。⭕共享资源好比一个单人使用的公共卫生间⭕锁相当于卫生间的门锁机制⭕线程就像排队等待使用卫生间的人2、为什么需要锁核心原因是为了防止并发访问共享资源时出现的数据不一致、逻辑错误或系统崩溃。 如果没有锁多个线程同时读写同一份数据就会引发一系列严重问题。最主要的问题是 “竞态条件”。✔️比如A在使用公共卫生间单人间时一旦从内部锁上门就表示该空间已被占用此时B不得强行进入。3、举个栗子Slf4j public class SyncThreadDemo { private static int count 0; public static void add() { count; } public static void reduce() { count--; } public static void main(String[] args) throws InterruptedException { Thread t1 new Thread(() - { for (int i 0; i 100; i) { add(); } }, t1); Thread t2 new Thread(() - { for (int i 0; i 100; i) { reduce(); } }, t2); t1.start(); t2.start(); t1.join(); t2.join(); log.info(计算后结果{}, count); } }输出结果[main] INFO com.nl.threadway.SyncThreadDemo - 计算后结果-44✏️ 两个线程同时对同一变量进行100次增减操作时若不加锁控制最终结果可能是正数、负数、零二、synchronized的使用1、举个栗子private static Object lock new Object(); private static int count 0; public static void add() { synchronized (lock) { count; } } public static void reduce() { synchronized (lock) { count--; } }✔️只需为这两个方法的代码块添加 synchronized 关键字即可确保输出结果始终为 0三、synchronized原理分析1、实现原理在 Java 中每个对象都有一个内置的Monitor监视器锁也称为内部锁或互斥锁当线程进入 synchronized 修饰的方法或代码块时它会自动获取这个锁。退出时正常退出或抛出异常会自动释放锁。 synchronized是JVM内置锁基于Monitor机制实现依赖底层操作系统的互斥原语Mutex互斥量它是一个重量级锁性能较低1.1、synchronized的JVM 字节码指令分析在 IntelliJ IDEA 中安装jclasslib插件后可以查看 add 方法对应的 JVM 字节码指令其中出现的 monitorenter 和 monitorexit 指令清晰地表明该方法基于 Monitor 机制实现。解析⭕monitorenter进入obj的监视器⭕monitorexit 退出obj的监视器1.2、当synchronized代码块执行过程中发生异常时系统会如何处理细心的读者可能会注意到字节码中出现了两个monitorexit指令。这是编译器自动生成的异常处理机制确保即使在异常情况下锁也能被正确释放具体逻辑如下try { monitorenter // 临界区代码 } finally { monitorexit // 保证一定会执行 }2、Monitor管程/监视器机制详解Monitor是管程也可以称为监视器是一种用于多线程同步的机制它确保在同一时刻只有一个线程可以执行管程中的某个子程序或代码块管程是基于MESA模型实现的管程中引入了条件变量的概念而且每个条件变量都对应有一个等待队列MESA 模型中条件变量可以有多个Java 语言内置的管程里只有一个条件变量。模型如下图所示✔️入口等待队列多线程进入的时排队只允许一个线程进入管程内部其他线程等待✔️条件变量和等待队列解决线程同步的问题3、synchronized的锁Object对象在Java中每个对象都可以作为监视器锁Monitor Lock但这不是说对象本身就是锁而是说每个Java对象内部都有一个与锁相关的ObjectMonitor数据结构 Object的方法wait、notify、notifyAll都是调用native 的方法实现public final native void wait(long timeout) throws InterruptedException; public final native void notify(); public final native void notifyAll()⭕方法定义在 Object 类中是所有 Java 对象都拥有的方法⭕方法被标记为 native意味着其实现是在 JVM 的本地代码中通常是 C/C 实现⭕具体的实现依赖于底层操作系统和 JVM 实现4、ObjectMonitor结构ObjectMonitor() { // 用来保存锁对象的mark word的值。因为object里面已经不保存mark word的原来的值了保存的是ObjectMonitor对象的地址信息。当所有线程都完成了之后需要销毁掉ObjectMonitor的时候需要将原有的header里面的值重新复制到mark word中来。 _header NULL; _count 0; _waiters 0, // 锁的重入次数 _recursions 0; // 指向的是对象的地址信息方便通过ObjectMonitor来访问对应的锁对象。 _object NULL; // 标识拥有该monitor的线程当前获取锁的线程 _owner NULL; // 等待线程调用wait组成的双向循环链表_WaitSet是第一个节点 _WaitSet NULL; _WaitSetLock 0 ; _Responsible NULL ; _succ NULL ; //多线程竞争锁会先存到这个单向链表中 FILO栈结构 _cxq NULL ; FreeNext NULL ; //队列用来获取锁的缓冲区存放在进入或重新进入时被阻塞(blocked)的线程 (也是存竞争锁失败的线程) //将cxq和waitSet中的数据 移动到entryList进行排队。这个统一获取锁的入口。一般是cxq 或者waitSet数据复制过来进行统一排队。 _EntryList NULL ; _SpinFreq 0 ; _SpinClock 0 ; OwnerIsThread 0 ; _previous_owner_tid 0; }⚠️需要重要关注的属性⭕_recursions和_owner 线程 可重入的实现⭕_EntryList入口等待队列将符合条件的cxq和waitSet中的数据 移动到entryList进行排队⭕_WaitSet 等待队列⭕_cxq 多线程竞争时会先进入这个队列5、重量级锁实现原理synchronized 的底层实现基于 monitor 对象、CAS 操作和 mutex 互斥锁机制。其内部维护两种队列等待队列cxq 和 EntryList存储未获取锁的线程条件等待队列waitSet存放调用 wait() 方法的线程5.1、_WaitSet等待队列调用 notifyAll()当线程释放锁或调用 notify 时会唤醒对应队列中的线程重新竞争锁。由于线程的阻塞和唤醒需要操作系统介入涉及用户态与内核态的切换这种系统调用的高开销使得 synchronized 被称为重量级锁。提醒⭕为了避免多个线程同时竞争锁带来的性能问题系统采用了两个链表结构_cxq 是一个基于 CAS 操作的单向链表用于临时存储并发竞争的线程而 _EntryList 是一个 双向链表在每次唤醒操作时会将部分线程节点从 _cxq 迁移过来从而减轻 _cxq 链表的尾部竞争压力使用notifyAll()而非notify()的主要考虑是避免线程饥饿问题。虽然一次唤醒一个线程确实能减少上下文切换次数但notify()的随机唤醒机制可能导致某些线程长时间得不到执行package com.nl; import lombok.extern.slf4j.Slf4j; Slf4j public class SimpleNotifyLost { final static Object lock new Object(); static volatile boolean condition false; public static void main(String[] args) throws InterruptedException { new Thread(() - { log.debug(A开始执行); synchronized (lock) { log.debug(A获取锁); try { // 让线程在lock上一直等待下去 lock.wait(); log.debug(A被唤醒); if (condition) { lock.wait(); } condition true; log.debug(A执行结束); } catch (InterruptedException e) { e.printStackTrace(); } log.debug(A执行完成); } }, A).start(); new Thread(() - { log.debug(B开始执行); synchronized (lock) { log.debug(B获取锁); try { // 让线程在lock上一直等待下去 lock.wait(); log.debug(B被唤醒); if (!condition) { lock.wait(); } log.debug(B执行结束); } catch (InterruptedException e) { e.printStackTrace(); } log.debug(B执行完成); } }, B).start(); // 主线程两秒后执行 Thread.sleep(2000); synchronized (lock) { // 随机唤醒一个线程 lock.notify(); // 唤醒lock所有等待线程 // lock.notifyAll(); } } }执行结果如下 A可以执行完成23:08:34.091 [A] DEBUG com.nl.SimpleNotifyLost - A开始执行 23:08:34.091 [B] DEBUG com.nl.SimpleNotifyLost - B开始执行 23:08:34.093 [A] DEBUG com.nl.SimpleNotifyLost - A获取锁 23:08:34.093 [B] DEBUG com.nl.SimpleNotifyLost - B获取锁 23:08:36.104 [A] DEBUG com.nl.SimpleNotifyLost - A被唤醒 23:08:36.104 [A] DEBUG com.nl.SimpleNotifyLost - A执行结束 23:08:36.104 [A] DEBUG com.nl.SimpleNotifyLost - A执行完成B被随机唤醒获取到锁但是不满足条件就一直阻塞了然后一直持有锁也不释放23:08:56.708 [B] DEBUG com.nl.SimpleNotifyLost - B开始执行 23:08:56.708 [A] DEBUG com.nl.SimpleNotifyLost - A开始执行 23:08:56.710 [B] DEBUG com.nl.SimpleNotifyLost - B获取锁 23:08:56.710 [A] DEBUG com.nl.SimpleNotifyLost - A获取锁 23:08:58.709 [B] DEBUG com.nl.SimpleNotifyLost - B被唤醒如果使用的是notifyAll23:12:52.114 [B] DEBUG com.nl.SimpleNotifyLost - B开始执行 23:12:52.114 [A] DEBUG com.nl.SimpleNotifyLost - A开始执行 23:12:52.115 [B] DEBUG com.nl.SimpleNotifyLost - B获取锁 23:12:52.115 [A] DEBUG com.nl.SimpleNotifyLost - A获取锁 23:12:54.125 [A] DEBUG com.nl.SimpleNotifyLost - A被唤醒 23:12:54.126 [A] DEBUG com.nl.SimpleNotifyLost - A执行结束 23:12:54.126 [A] DEBUG com.nl.SimpleNotifyLost - A执行完成 23:12:54.126 [B] DEBUG com.nl.SimpleNotifyLost - B被唤醒 23:12:54.127 [B] DEBUG com.nl.SimpleNotifyLost - B执行结束 23:12:54.127 [B] DEBUG com.nl.SimpleNotifyLost - B执行完成⚠️⚠️提醒⭕notify()唤醒一个处于等待状态的线程。若该线程发现条件未满足如所需资源已被其他线程占用它可能再次进入等待状态即重新调用 wait()。此时若无其他线程调用 notify()可能导致所有等待线程陷入无限等待。⭕notifyAll()唤醒所有等待线程使每个线程都能检查当前条件。满足条件的线程继续执行不满足条件的则重新进入等待状态。这种方法有效防止了仅唤醒单个线程时可能出现的信号丢失问题即被唤醒线程因条件不满足而无法执行。但缺点是会导致较多的上下文切换开销。6、_EntryList队列线程的唤醒非公平synchronized的唤醒机制并不是完全由JVM控制的而是依赖于操作系统的线程调度。具体过程如下当锁释放时JVM会从_EntryList中选取一个线程将其标记为候选的下一个锁持有者这个选取过程并不保证公平可能是通过某种策略比如默认是非公平的可能选择最后进入的线程以减少上下文切换开销JVM会唤醒这个被选中的线程。注意这里的唤醒是指将线程从阻塞状态变为可运行状态然后线程会尝试获取锁。被唤醒的线程并不一定能立即获得锁因为此时可能有一个新的线程不在_EntryList中正在尝试获取锁并且可能成功获取。所以被唤醒的线程需要重新竞争锁。四、总结synchronized 是 JVM 内置的基于 Monitor 实现的锁机制。在高并发场景下由于频繁的锁竞争和上下文切换其性能开销较大属于重量级锁。为此官方对 synchronized 进行了锁膨胀升级的优化过程这部分内容我们将在后续详细讲解。synchronized加锁的方式在并发量大时性能会受影响而 CAS 这种无锁的操作方式能有效解决这个问题。