许彩 發表於 2025-9-27 12:37:00

JUC: 线程锁

<h3 id="1-面试题复盘">1 面试题复盘</h3>
<ul>
<li>
<p>如何理解多线程,如何处理并发,线程池有哪些核心参数?</p>
</li>
<li>
<p>Java加锁有哪几种锁?</p>
</li>
<li>
<p>synchronized原理是什么?为什么可重入?如何获取对象的锁?</p>
</li>
<li>
<p>JVM对原生锁做了哪些优化?</p>
</li>
<li>
<p>什么是锁清除和锁粗化?</p>
</li>
<li>
<p>乐观锁是什么?synchronized与乐观锁什么区别?</p>
</li>
<li>
<p>volatile有什么作用?</p>
</li>
<li>
<p>ReentrantLock原理是什么?</p>
</li>
<li>
<p>AQS框架原理介绍一下?</p>
</li>
<li>
<p>简单说说Lock</p>
</li>
<li>
<p>是否使用过CountDownLanch? 如何使用?</p>
</li>
</ul>
<h3 id="2-乐观锁与悲观锁">2 乐观锁与悲观锁</h3>
<h4 id="1悲观锁">(1)悲观锁</h4>
<p>synchronized和Lock都是悲观锁, 同一时间点,有且只有一个线程能够访问对应的资源。 写操作多的场景使用。</p>
<h4 id="2乐观锁">(2)乐观锁</h4>
<p>认为自己在使用数据时,不会有别的线程修改数据或资源,所以不会添加锁。只是在更新资源的时候,需要去判断当前数据有没有别的线程更新过。判断规则有:</p>
<ul>
<li>版本号机制version,每一次更新一个版本号。</li>
<li>采用CAS算法,Java原子类中的递增操作就通过<code>CAS</code>自旋实现的。比较并交换</li>
</ul>
<h3 id="3-锁是什么">3 锁是什么</h3>
<h4 id="1锁案例演示----synchronized的三种应用方式">(1)锁案例演示- synchronized的三种应用方式</h4>
<pre><code class="language-java">// 1. 对象锁:对于非静态方法使用synchronized就是加的对象锁,获得的是这个对象(this)作为锁
public synchronized void sentEmail(){
    try {
      TimeUnit.SECONDS.sleep(4);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    System.out.println("sent email");
}

// 2. 类锁:对于静态方法或使用synchronized就是加的类锁,获得的是这个类对象(.class)作为锁
public static synchronized void sentSMS(){
    System.out.println("sent SMS");
}

// 3. 代码块,使用的是synchronized括号内的对象
public void testSynchronized(){
    synchronized (this){
      System.out.println("testSynchronized");
    }
}
</code></pre>
<h4 id="2从字节码角度分析synchronized实现">(2)从字节码角度分析synchronized实现</h4>
<ul>
<li><code>javap -c ***.class</code>文件反编译</li>
</ul>
<pre><code class="language-sh">javap -c a.class #         ​​反汇编代码​​,输出每个方法的 Java 字节码指令(指令集)
-v或 -verbose # 输出​​最详细的附加信息​​,包括版本号、常量池、方法描述符(签名)、栈大小等
</code></pre>
<ul>
<li><code>synchronized</code>同步代码块</li>
</ul>
<pre><code class="language-java">public void testSynchronized(){
    synchronized (this){
      System.out.println("testSynchronized");
    }
}

</code></pre>
<pre><code class="language-assembly">public void testSynchronized();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: monitorenter # 获取锁
       4: getstatic   #26               // Field java/lang/System.out:Ljava/io/PrintStream;
       7: ldc         #44               // String testSynchronized
       9: invokevirtual #34               // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: aload_1
      13: monitorexit # 释放锁
      14: goto          22
      17: astore_2
      18: aload_1
      19: monitorexit # 异常情况也可以释放锁
      20: aload_2
      21: athrow
      22: return
    Exception table:
       from    totarget type
         4    14    17   any
          17    20    17   any
}
</code></pre>
<ul>
<li><code>synchronized</code>对象锁</li>
</ul>
<p>-v</p>
<pre><code class="language-java">public synchronized void sentEmail(){
    System.out.println("sent email");
}

</code></pre>
<pre><code class="language-assembly">public synchronized void sentEmail();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED # 会检查对象的ACC_SYNCHRONIZED是否被设置,如果设置了,则会持有monitor直到方法完成释放
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic   #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc         #13               // String sent email
         5: invokevirtual #15               // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
      line 31: 0
      line 32: 8
      LocalVariableTable:
      StartLengthSlotName   Signature
            0       9   0this   Lcom/thread/sgg/juc/Phone;

</code></pre>
<ul>
<li><code>synchronized</code>类锁</li>
</ul>
<pre><code class="language-java">public static synchronized void sentSMS(){
    System.out.println("sent SMS");
}
</code></pre>
<pre><code class="language-assembly">public static synchronized void sentSMS();
    descriptor: ()V
    flags: (0x0029) ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED # 根据是否有ACC_STATIC是否存在判断应该是类锁还是对象锁
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic   #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc         #21               // String sent SMS
         5: invokevirtual #15               // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
      line 34: 0
      line 35: 8


</code></pre>
<h4 id="3反编译synchronized锁的是什么">(3)反编译synchronized锁的是什么</h4>
<p>为什么任何一个对象都可以成为一个锁?</p>
<p>Object是任何类的父类</p>
<p>什么是管程monitor?</p>
<p>monitor是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。</p>
<p>这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。</p>
<pre><code class="language-c++">//结构体如下
ObjectMonitor::ObjectMonitor() {
_header       = NULL;
_count       = 0;   // 该线程获取锁的次数
_waiters      = 0,
_recursions   = 0;       //线程的重入次数
_object       = NULL;
_owner      = NULL;    //标识拥有该monitor的线程
_WaitSet      = NULL;    //等待线程组成的双向循环链表,_WaitSet是第一个节点
_WaitSetLock= 0 ;
_Responsible= NULL ;
_succ         = NULL ;
_cxq          = NULL ;    //多线程竞争锁进入时的单向链表
FreeNext      = NULL ;
_EntryList    = NULL ;    //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
_SpinFreq   = 0 ;
_SpinClock    = 0 ;
OwnerIsThread = 0 ;
}
</code></pre>
<h3 id="4-公平锁与非公平锁">4 公平锁与非公平锁</h3>
<p>公平锁:多个线程按照申请锁的顺序获取锁</p>
<p>非公平锁:不按照顺序获取锁,可能导致某些线程处于饥饿状态</p>
<p>为什么有公平和非公平锁的设计?为什么默认非公平锁?</p>
<p>非公平锁能减少线程切换,减少CPU空闲状态的时间,效率较高。</p>
<pre><code class="language-java">public class ReentrantLock implements Lock, java.io.Serializable {
    abstract static class Sync extends AbstractQueuedSynchronizer {
      ...
    }
      
    /**
   * Sync object for non-fair locks
   */
    static final class NonfairSync extends Sync {
                ...
    }

    /**
   * Sync object for fair locks
   */
    static final class FairSync extends Sync {
      ...
    }
    public ReentrantLock() {
      sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
      sync = fair ? new FairSync() : new NonfairSync();
    }
</code></pre>
<h3 id="5-可重入锁递归锁">5 可重入锁(递归锁)</h3>
<p>是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。</p>
<ul>
<li>可重入锁的种类
<ul>
<li>隐式:synchronized(自动多次释放)</li>
<li>显示:ReentrantLock(手动多次释放)</li>
</ul>
</li>
</ul>
<h3 id="6-死锁及排查">6 死锁及排查</h3>
<p>死锁是指两个或两个以上的线程在执行过程中,<code>因争夺双方持有的资源而造成的一种互相等待的现象</code>,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足!死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。</p>
<pre><code class="language-sh">jps# 查看Java进程编号
jstack 进程编号 # 查看死锁情况
</code></pre>
<h3 id="7-写锁独占与读锁共享">7 写锁(独占)与读锁(共享)</h3>
<h3 id="8-自旋锁spinlock">8 自旋锁SpinLock</h3>
<h3 id="9-无锁-独占锁-读写锁-邮戳锁">9 无锁-独占锁-读写锁-邮戳锁</h3>
<h3 id="10-无锁-偏向锁-轻量锁-重量锁">10 无锁-偏向锁-轻量锁-重量锁</h3><br><br>
来源:https://www.cnblogs.com/fei1010/p/19114969
頁: [1]
查看完整版本: JUC: 线程锁