锁的八个问题

探讨两个问题:synchronized用的是否是同一把锁?以及锁的范围是什么?

  1. class Phone {
  2. public static synchronized void sendSMS() throws Exception {
  3. //停留 4 秒
  4. TimeUnit.SECONDS.sleep(4);
  5. System.out.println("------sendSMS");
  6. }
  7. public synchronized void sendEmail() throws Exception {
  8. System.out.println("------sendEmail");
  9. }
  10. public void getHello() {
  11. System.out.println("------getHello");
  12. }
  13. }
  14. /**
  15. * @Description: 8 锁
  16. *
  17. 1 标准访问,先打印短信还是邮件
  18. ------sendSMS
  19. ------sendEmail
  20. 2 停 4 秒在短信方法内,先打印短信还是邮件
  21. ------sendSMS
  22. ------sendEmail
  23. 3 新增普通的 hello 方法,是先打短信还是 hello
  24. ------getHello
  25. ------sendSMS
  26. 4 现在有两部手机,先打印短信还是邮件
  27. ------sendEmail
  28. ------sendSMS
  29. 5 两个静态同步方法,1 部手机,先打印短信还是邮件
  30. ------sendSMS
  31. ------sendEmai
  32. 6 两个静态同步方法,2 部手机,先打印短信还是邮件
  33. ------sendSMS
  34. ------sendEmail
  35. 7 1 个静态同步方法,1 个普通同步方法,1 部手机,先打印短信还是邮件
  36. ------sendEmail
  37. ------sendSMS
  38. 8 1 个静态同步方法,1 个普通同步方法,2 部手机,先打印短信还是邮件
  39. ------sendEmail
  40. ------sendSMS

分析:1,2是因为手握同一把锁this,所以是先后执行。3是hello方法并未加锁,而sms睡了4s,则hello方法先打印
4是因为两个手机,两个方法手握两把锁,所以先输出email;5,6手握的Class字节码对象,是同一把锁,所以先打印sms,7,8不是一把锁且范围不一样,类似大楼和房间
image.png

结论: 一个对象里面如果有多个 synchronized 方法,某一个时刻内,只要一个线程去调用其中的 一个 synchronized 方法了, 其它的线程都只能等待

静态同步方法与非静态同步方法之间是不会有竞态条件的。

公平锁和非公平锁

  1. private final ReentrantLock lock = new ReentrantLock(true);
  2. ---------------------------------------
  3. private final ReentrantLock lock = new ReentrantLock(false);
  4. 公平锁:会造成其他线程被饿死的情况,效率高
  5. 非公平锁:其他线程不会出现饿死现象,效率低

image.png

  1. Re的无参构造和有参构造

    1. public ReentrantLock() {
    2. sync = new NonfairSync();
    3. }
    4. //无参构造会默认生产非公平锁
    5. /**
    6. * Creates an instance of {@code ReentrantLock} with the
    7. * given fairness policy.
    8. *
    9. * @param fair {@code true} if this lock should use a fair ordering policy
    10. */
    11. public ReentrantLock(boolean fair) {
    12. sync = fair ? new FairSync() : new NonfairSync();
    13. }
    14. //有参构造会根据传入的布尔值来生产公平或非公平锁
  2. 公平锁源码

    static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    
    /**
    * Acquires only if reentrant or queue is empty.
    */
    final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
    if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
      setExclusiveOwnerThread(current);
       return true;
     }
     } else if (getExclusiveOwnerThread() == current) {
       if (++c < 0) // overflow
           throw new Error("Maximum lock count exceeded");
          setState(c);
          return true;
        }
     return false;
    }
    //公平锁会先进行判断,这是其实现公平的原因,也是其效率低的原因
    

    可重入锁

  3. 可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没有释放而阻塞.

  4. synchronized和lock都是可重入锁;sychronized是隐式锁,不用手工上锁与解锁,而lock为显示锁,需要手工上锁与解锁。

image.png
代码验证synchronized和ReentrantLock是可重入锁:

Object o = new Object();
new Thread(()->{
    synchronized(o) {
        System.out.println(Thread.currentThread().getName()+" 外层");

        synchronized (o) {
            System.out.println(Thread.currentThread().getName()+" 中层");

            synchronized (o) {
                System.out.println(Thread.currentThread().getName()+" 内层");
            }
        }
    }

},"t1").start();
=====================================
  调用线程:三个打印都会输出
public class SyncLockDemo {

    public synchronized void add() {
        add();
    }

    public static void main(String[] args) {
        //Lock演示可重入锁
        Lock lock = new ReentrantLock();
        //创建线程
        new Thread(()->{
            try {
                //上锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+" 外层");

                try {
                    //上锁
                    lock.lock();
                    System.out.println(Thread.currentThread().getName()+" 内层");
                }finally {
                    //释放锁
                    //若注释掉此行,则"aaaa"就不会再执行,因为当前线程的锁尚未释放
                    lock.unlock();
                }
            }finally {
                //释放做
                lock.unlock();
            }
        },"t1").start();

        //创建新线程
        new Thread(()->{
            lock.lock();
            System.out.println("aaaa");
            lock.unlock();
        },"aa").start();
        }
 }
=================================

   因为两个线程使用的是同一把锁,若线程1没有unlock,则线程2就会一直等待其锁的释放

⑤. Synchronized的重入的实现机理 (为什么任何一个对象都可以成为一个锁)

  • 每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针
  • 当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将计数器加1
  • 在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程时当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直到持有线程释放该锁
  • 当执行monitorexit,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已经释放


死锁

什么是死锁

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去
如果资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁
image.png
线程A手握锁A,线程B手握锁B,但是线程A却想要获取锁B,线程B却想要获取锁A。但是线程A必须获取锁B后才会释放锁A让线程B获取,但线程B不会释放锁B,因为它还没获取锁A。这样就会出现一直僵持的情况

public class DeadLockDemo{

    static Object lockA = new Object();
    static Object lockB = new Object();
    public static void main(String[] args){
        Thread a = new Thread(() -> {
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "\t" + " 自己持有A锁,期待获得B锁");

                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "\t 获得B锁成功");
                }
            }
        }, "a");
        a.start();

        new Thread(() -> {
            synchronized (lockB){

                System.out.println(Thread.currentThread().getName()+"\t"+" 自己持有B锁,期待获得A锁");

                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

                synchronized (lockA){

                    System.out.println(Thread.currentThread().getName()+"\t 获得A锁成功");
                }
            }
        },"b").start();


    }
}

产生死锁原因

  1. 系统资源不足
  2. 进程运行推进的顺序不合适
  3. 资源分配不当

    验证是否是死锁

  4. jps 查看进程号

  5. jstack jvm自带的堆栈跟踪工具 输出详细信息 ```bash Microsoft Windows [版本 10.0.19043.1165] (c) Microsoft Corporation。保留所有权利。

C:\Windows\system32>jps 24792 Jps

C:\Windows\system32>jps 9456 16228 Jps 9252 RemoteMavenServer36 21704 Launcher 5756 DeadLockDemo

C:\Windows\system32>jstack 5756 2021-09-20 09:48:51 Full thread dump OpenJDK 64-Bit Server VM (25.302-b08 mixed mode):

“DestroyJavaVM” #14 prio=5 os_prio=0 tid=0x000001b817f4b000 nid=0x5c58 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE

“b” #13 prio=5 os_prio=0 tid=0x000001b834f97000 nid=0x4f38 waiting for monitor entry [0x0000007d645ff000] java.lang.Thread.State: BLOCKED (on object monitor) at DeadLockDemo.lambda$main$1(DeadLockDemo.java:38)

    - waiting to lock <0x000000076ba87dc0> (a java.lang.Object)
    - locked <0x000000076ba87dd0> (a java.lang.Object)
    at DeadLockDemo$$Lambda$2/1078694789.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

“a” #12 prio=5 os_prio=0 tid=0x000001b834f94800 nid=0x37dc waiting for monitor entry [0x0000007d644ff000] java.lang.Thread.State: BLOCKED (on object monitor) at DeadLockDemo.lambda$main$0(DeadLockDemo.java:23)

    - waiting to lock <0x000000076ba87dd0> (a java.lang.Object)
    - locked <0x000000076ba87dc0> (a java.lang.Object)
    at DeadLockDemo$$Lambda$1/1324119927.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

“Service Thread” #11 daemon prio=9 os_prio=0 tid=0x000001b834bf2800 nid=0x1e1c runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE

“C1 CompilerThread3” #10 daemon prio=9 os_prio=2 tid=0x000001b834bed000 nid=0x31e0 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE

“C2 CompilerThread2” #9 daemon prio=9 os_prio=2 tid=0x000001b834bea000 nid=0x6254 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE

“C2 CompilerThread1” #8 daemon prio=9 os_prio=2 tid=0x000001b834be9800 nid=0x2d5c waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE

“C2 CompilerThread0” #7 daemon prio=9 os_prio=2 tid=0x000001b834be6800 nid=0x2b74 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE

“Monitor Ctrl-Break” #6 daemon prio=5 os_prio=0 tid=0x000001b834be0800 nid=0x224c runnable [0x0000007d63dfe000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:171) at java.net.SocketInputStream.read(SocketInputStream.java:141) at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)

    - locked <0x000000076bbc73f0> (a java.io.InputStreamReader)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    - locked <0x000000076bbc73f0> (a java.io.InputStreamReader)
    at java.io.BufferedReader.readLine(BufferedReader.java:389)
    at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:49)

“Attach Listener” #5 daemon prio=5 os_prio=2 tid=0x000001b8338c3000 nid=0x54b0 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE

“Signal Dispatcher” #4 daemon prio=9 os_prio=2 tid=0x000001b8338c0800 nid=0x48ec runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE

“Finalizer” #3 daemon prio=8 os_prio=1 tid=0x000001b83383b800 nid=0x2db0 in Object.wait() [0x0000007d63aff000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method)

    - waiting on <0x000000076b909508> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
    - locked <0x000000076b909508> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

“Reference Handler” #2 daemon prio=10 os_prio=2 tid=0x000001b833834000 nid=0x2d8 in Object.wait() [0x0000007d639ff000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method)

    - waiting on <0x000000076b907118> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    - locked <0x000000076b907118> (a java.lang.ref.Reference$Lock)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

“VM Thread” os_prio=2 tid=0x000001b833802800 nid=0x4918 runnable

“GC task thread#0 (ParallelGC)” os_prio=0 tid=0x000001b817f60800 nid=0x52b0 runnable

“GC task thread#1 (ParallelGC)” os_prio=0 tid=0x000001b817f62800 nid=0x4d0c runnable

“GC task thread#2 (ParallelGC)” os_prio=0 tid=0x000001b817f64000 nid=0x25d8 runnable

“GC task thread#3 (ParallelGC)” os_prio=0 tid=0x000001b817f65800 nid=0x2a98 runnable

“GC task thread#4 (ParallelGC)” os_prio=0 tid=0x000001b817f69000 nid=0x5828 runnable

“GC task thread#5 (ParallelGC)” os_prio=0 tid=0x000001b817f6a800 nid=0x5f0c runnable

“GC task thread#6 (ParallelGC)” os_prio=0 tid=0x000001b817f6d800 nid=0x4e18 runnable

“GC task thread#7 (ParallelGC)” os_prio=0 tid=0x000001b817f6f000 nid=0x63a0 runnable

“VM Periodic Task Thread” os_prio=2 tid=0x000001b834bf5800 nid=0x3b98 waiting on condition

JNI global references: 317

Found one Java-level deadlock:

“b”: waiting to lock monitor 0x000001b83383a758 (object 0x000000076ba87dc0, a java.lang.Object), which is held by “a” “a”: waiting to lock monitor 0x000001b8338380d8 (object 0x000000076ba87dd0, a java.lang.Object), which is held by “b”

Java stack information for the threads listed above:

“b”: at DeadLockDemo.lambda$main$1(DeadLockDemo.java:38)

    - waiting to lock <0x000000076ba87dc0> (a java.lang.Object)
    - locked <0x000000076ba87dd0> (a java.lang.Object)
    at DeadLockDemo$$Lambda$2/1078694789.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

“a”: at DeadLockDemo.lambda$main$0(DeadLockDemo.java:23)

    - waiting to lock <0x000000076ba87dd0> (a java.lang.Object)
    - locked <0x000000076ba87dc0> (a java.lang.Object)
    at DeadLockDemo$$Lambda$1/1324119927.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

C:\Windows\system32> ```