05-公平锁和非公平锁,死锁,可重入锁

  1. 一、synchronized 锁的8种情况
  2. 二、公平锁和非公平锁
  3. 三、可重入锁
  4. 四、死锁

一、synchronized 锁的8种情况

下面通过一段代码,演示 synchronized 锁的 8 种情况

  1. 标准访问,先打印短信还是邮件

    class Phone {
    
        public synchronized void sendSMS() throws Exception {
            System.out.println("------sendSMS");
        }
    
        public synchronized void sendEmail() throws Exception {
            System.out.println("------sendEmail");
        }
    }
    
    public class Lock_8 {
        public static void main(String[] args) throws Exception {
            Phone phone = new Phone();
    
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "AA").start();
    
            Thread.sleep(100);
    
            new Thread(() -> {
                try {
                    phone.sendEmail();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "BB").start();
        }
    }

    打印结果:

    ------sendSMS
    ------sendEmail

    分析:两个方法都加了 synchronized 关键字,因为是同一个对象调用,所有是同一把锁,按照顺序执行。

  2. 停4秒在短信方法内,先打印短信还是邮件

    class Phone {
    
        public synchronized void sendSMS() throws Exception {
            //停留4秒
            TimeUnit.SECONDS.sleep(4);
            System.out.println("------sendSMS");
        }
    
        public synchronized void sendEmail() throws Exception {
            System.out.println("------sendEmail");
        }
    }
    
    public class Lock_8 {
        public static void main(String[] args) throws Exception {
            Phone phone = new Phone();
    
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "AA").start();
    
            Thread.sleep(100);
    
            new Thread(() -> {
                try {
                    phone.sendEmail();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "BB").start();
        }
    }

    打印结果:

    ------sendSMS
    ------sendEmail

    分析:两个方法都加了 synchronized 关键字,因为还是同一个对象调用,所有还是同一把锁,先拿到锁的方法执行结束后,另一个方法才能执行。

  3. 新增普通的hello方法,是先打短信还是hello

    class Phone {
    
        public synchronized void sendSMS() throws Exception {
            //停留4秒
            TimeUnit.SECONDS.sleep(4);
            System.out.println("------sendSMS");
        }
    
        public synchronized void sendEmail() throws Exception {
            System.out.println("------sendEmail");
        }
    
        public void getHello() {
            System.out.println("------getHello");
        }
    }
    
    public class Lock_8 {
        public static void main(String[] args) throws Exception {
            Phone phone = new Phone();
    
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "AA").start();
    
            Thread.sleep(100);
    
            new Thread(() -> {
                try {
                    phone.getHello();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "BB").start();
        }
    }

    打印结果:

    ------getHello
    ------sendSMS

    分析:hello方法没有加锁,调用即执行,sendSMS有sleep阻塞。

  4. 现在有两部手机,先打印短信还是邮件

    class Phone {
        public synchronized void sendSMS() throws Exception {
            //停留4秒
            TimeUnit.SECONDS.sleep(4);
            System.out.println("------sendSMS");
        }
    
        public synchronized void sendEmail() throws Exception {
            System.out.println("------sendEmail");
        }
    }
    
    public class Lock_8 {
        public static void main(String[] args) throws Exception {
    
            Phone phone = new Phone();
            Phone phone2 = new Phone();
    
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "AA").start();
    
            Thread.sleep(100);
    
            new Thread(() -> {
                try {
                    phone2.sendEmail();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "BB").start();
        }
    }

    打印结果:

    ------sendEmail
    ------sendSMS

    分析:synchronized 在方法上,是方法级的锁,由于是两个 phone 对象调用不同的方法,所以 sendSMS() 和 sendEmail() 持有的是不同的锁,互不影响。

  5. 两个静态同步方法,1部手机,先打印短信还是邮件

    class Phone {
        public static synchronized void sendSMS() throws Exception {
            //停留4秒
            TimeUnit.SECONDS.sleep(4);
            System.out.println("------sendSMS");
        }
    
        public static synchronized void sendEmail() throws Exception {
            System.out.println("------sendEmail");
        }
    }
    
    public class Lock_8 {
        public static void main(String[] args) throws Exception {
            Phone phone = new Phone();
            
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "AA").start();
    
            Thread.sleep(100);
    
            new Thread(() -> {
                try {
                    phone.sendEmail();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "BB").start();
        }
    }

    打印结果:

    ------sendSMS
    ------sendEmail

    分析:synchronized 在静态方法上,是类级别的锁,所以两个方法持有的是同一把锁,要按顺序执行

  6. 两个静态同步方法,2部手机,先打印短信还是邮件

    class Phone {
    
        public static synchronized void sendSMS() throws Exception {
            //停留4秒
            TimeUnit.SECONDS.sleep(4);
            System.out.println("------sendSMS");
        }
    
        public static synchronized void sendEmail() throws Exception {
            System.out.println("------sendEmail");
        }
    }
    
    public class Lock_8 {
        public static void main(String[] args) throws Exception {
    
            Phone phone = new Phone();
            Phone phone2 = new Phone();
    
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "AA").start();
    
            Thread.sleep(100);
    
            new Thread(() -> {
                try {
                    phone2.sendEmail();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "BB").start();
        }
    }

    打印结果:

    ------sendSMS
    ------sendEmail

    分析:synchronized 在静态方法上,是类级别的锁,所以两个方法持有的是同一把锁,要按顺序执行

  7. 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件

    class Phone {
    
        public static synchronized void sendSMS() throws Exception {
            //停留4秒
            TimeUnit.SECONDS.sleep(4);
            System.out.println("------sendSMS");
        }
    
        public void getHello() {
            System.out.println("------getHello");
        }
    }
    
    public class Lock_8 {
        public static void main(String[] args) throws Exception {
            Phone phone = new Phone();
    
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "AA").start();
    
            Thread.sleep(100);
    
            new Thread(() -> {
                try {
                    phone.getHello();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "BB").start();
        }
    }

    打印结果:

    ------getHello
    ------sendSMS

    分析:类加锁对普通方法无限制,普通方法调用即执行。

  8. 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件

    class Phone {
    
        public static synchronized void sendSMS() throws Exception {
            //停留4秒
            TimeUnit.SECONDS.sleep(4);
            System.out.println("------sendSMS");
        }
    
        public void sendEmail() throws Exception {
            System.out.println("------sendEmail");
        }
    }
    
    public class Lock_8 {
        public static void main(String[] args) throws Exception {
            Phone phone = new Phone();
            Phone phone2 = new Phone();
    
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "AA").start();
    
            Thread.sleep(100);
    
            new Thread(() -> {
                try {
                    phone2.sendEmail();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "BB").start();
        }
    }

    打印结果:

    ------sendEmail
    ------sendSMS

    分析:类加锁对普通方法无限制,普通方法调用即执行。

总结:

  • synchronized 锁的是方法,则是对象锁,同个对象锁的机制要等待,不同对象锁的机制调用同一个不用等待
  • 加了static则为class锁而不是对象锁
  • 对于同步方法块,锁是 synchronized 括号里配置对象

二、公平锁和非公平锁

  • 公平锁:效率相对低 ,但是cpu 的利用高了
  • 非公平锁:效率高,但是线程容易饿死(所有的工作,有一个线程完成)

用法: 在创建可重入锁时,向构造器中传入true

private final ReentrantLock lock = new ReentrantLock(true);

因为 ReentrantLock 的构造器源码如下:

/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

三、可重入锁

synchronizedlock 都是可重入锁

  • sychronized是隐式锁,不用手工上锁与解锁,而lock为显示锁,需要手工上锁与解锁
  • 可重入锁也叫递归锁

而且有了可重入锁之后,在拿到外层的第一把锁之后就可以一直进入到内层结构

嵌套实现代码 他能进入下一个锁内而不会出现死锁

synchronized的示例代码

/**
 * 演示可重入锁是什么意思,可重入,就是可以重复获取相同的锁而不会出现死锁
 * synchronized和ReentrantLock都是可重入的
 * */
public class WhatReentrantSynchronized {
    // 创建一个锁对象
    static Object mylock = new Object();
    public static void main(String[] args) {
        new Thread(()->{
            // 创建第一个锁
            synchronized (mylock){
                System.out.println("这是第一层锁");
                synchronized (mylock){
                    System.out.println("这是第二层锁");
                }
            }
        }).start();
    }
}

打印结果:

这是第一层锁
这是第二层锁

ReentrantLock的示例代码

/**
 * lock和unlock的数量必须一致,否则会出现死锁
 * */
public class WhatReentrantLock {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " 获取到第一把锁");
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " 获取到第二把锁");
                } finally {
                    lock.unlock();
                }
            } finally {
                lock.unlock();
            }
        }).start();
    }
}

打印结果

Thread-0 获取到第一把锁
Thread-0 获取到第二把锁

四、死锁

两个或以上的进程因为争夺资源而造成互相等待资源的现象称为死锁

死锁

产生死锁的原因:

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

我们有时候不知道是否是死锁 。那么怎么来验证呢? (电脑配置的有环境变量,在命令窗口)

  1. jps 类似于linux中的 ps -ef查看进程号
  2. jstack 自带的堆栈跟踪工具

具体死锁的操作代码实列

public class DeadLock {

    //创建两个对象
    static Object a = new Object();
    static Object b = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (a) {
                System.out.println(Thread.currentThread().getName()+" 持有锁a,试图获取锁b");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println(Thread.currentThread().getName()+" 获取锁b");
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (b) {
                System.out.println(Thread.currentThread().getName()+" 持有锁b,试图获取锁a");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a) {
                    System.out.println(Thread.currentThread().getName()+" 获取锁a");
                }
            }
        },"B").start();
    }
}

运行程序,结果如下:

A 持有锁a,试图获取锁b
B 持有锁b,试图获取锁a

程序处于暂停状态

使用 jstack 查看线程的情况

PS E:\testWorkspace\juc-demo> jps -l 
69860 sun.tools.jps.Jps
51436 com.atguigu.sync.DeadLock
# 当前执行的线程是 51436

# 使用jstack 查看线程信息
PS E:\testWorkspace\juc-demo> jstack 51436
2024-09-06 09:48:06
Full thread dump Java HotSpot(TM) 64-Bit Server VM (17.0.11+7-LTS-207 mixed mode, sharing):

Threads class SMR info:
_java_thread_list=0x000001f8fc26e0c0, length=15, elements={
0x000001f8b9f86ff0, 0x000001f8b9f87e70, 0x000001f8b9f9b8c0, 0x000001f8b9f9f2a0,
0x000001f8b9f9fc60, 0x000001f8b9fa1630, 0x000001f8b9fa23d0, 0x000001f8b9fb37d0,
0x000001f8b9fabf10, 0x000001f8b9f75300, 0x000001f8fc278d40, 0x000001f8fc279220,
0x000001f8fc296e80, 0x000001f8fc29c1b0, 0x000001f88d7a7660
}

"Reference Handler" #2 daemon prio=10 os_prio=2 cpu=0.00ms elapsed=196.10s tid=0x000001f8b9f86ff0 nid=0x10408 waiting on condition  [0x0000006af2dff000]
   java.lang.Thread.State: RUNNABLE
        at java.lang.ref.Reference.waitForReferencePendingList(java.base@17.0.11/Native Method)
        at java.lang.ref.Reference.processPendingReferences(java.base@17.0.11/Reference.java:253)
        at java.lang.ref.Reference$ReferenceHandler.run(java.base@17.0.11/Reference.java:215)

"Finalizer" #3 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=196.10s tid=0x000001f8b9f87e70 nid=0x9508 in Object.wait()  [0x0000006af2eff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(java.base@17.0.11/Native Method)
        - waiting on <0x000000062280d5d0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(java.base@17.0.11/ReferenceQueue.java:155)
        - locked <0x000000062280d5d0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(java.base@17.0.11/ReferenceQueue.java:176)
        at java.lang.ref.Finalizer$FinalizerThread.run(java.base@17.0.11/Finalizer.java:172)

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=196.09s tid=0x000001f8b9f9b8c0 nid=0x11780 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #5 daemon prio=5 os_prio=2 cpu=0.00ms elapsed=196.09s tid=0x000001f8b9f9f2a0 nid=0x11468 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread" #6 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=196.09s tid=0x000001f8b9f9fc60 nid=0x10d90 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Deflation Thread" #7 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=196.09s tid=0x000001f8b9fa1630 nid=0x116e0 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #8 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=196.09s tid=0x000001f8b9fa23d0 nid=0xb2f8 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"C1 CompilerThread0" #16 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=196.09s tid=0x000001f8b9fb37d0 nid=0x11040 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"Sweeper thread" #20 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=196.09s tid=0x000001f8b9fabf10 nid=0x11170 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Common-Cleaner" #21 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=196.09s tid=0x000001f8b9f75300 nid=0x10a7c in Object.wait()  [0x0000006af36fe000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(java.base@17.0.11/Native Method)
        - waiting on <0x00000006229199a8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(java.base@17.0.11/ReferenceQueue.java:155)
        - locked <0x00000006229199a8> (a java.lang.ref.ReferenceQueue$Lock)
        at jdk.internal.ref.CleanerImpl.run(java.base@17.0.11/CleanerImpl.java:140)
        at java.lang.Thread.run(java.base@17.0.11/Thread.java:842)
        at jdk.internal.misc.InnocuousThread.run(java.base@17.0.11/InnocuousThread.java:162)

"Monitor Ctrl-Break" #22 daemon prio=5 os_prio=0 cpu=0.00ms elapsed=196.07s tid=0x000001f8fc278d40 nid=0x11118 runnable  [0x0000006af3bfe000]
   java.lang.Thread.State: RUNNABLE
        at sun.nio.ch.SocketDispatcher.read0(java.base@17.0.11/Native Method)
        at sun.nio.ch.SocketDispatcher.read(java.base@17.0.11/SocketDispatcher.java:46)
        at sun.nio.ch.NioSocketImpl.tryRead(java.base@17.0.11/NioSocketImpl.java:266)
        at sun.nio.ch.NioSocketImpl.implRead(java.base@17.0.11/NioSocketImpl.java:317)
        at sun.nio.ch.NioSocketImpl.read(java.base@17.0.11/NioSocketImpl.java:355)
        at sun.nio.ch.NioSocketImpl$1.read(java.base@17.0.11/NioSocketImpl.java:808)
        at java.net.Socket$SocketInputStream.read(java.base@17.0.11/Socket.java:966)
        at sun.nio.cs.StreamDecoder.readBytes(java.base@17.0.11/StreamDecoder.java:270)
        at sun.nio.cs.StreamDecoder.implRead(java.base@17.0.11/StreamDecoder.java:313)
        at sun.nio.cs.StreamDecoder.read(java.base@17.0.11/StreamDecoder.java:188)
        - locked <0x0000000622afa510> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(java.base@17.0.11/InputStreamReader.java:177)
        at java.io.BufferedReader.fill(java.base@17.0.11/BufferedReader.java:162)
        at java.io.BufferedReader.readLine(java.base@17.0.11/BufferedReader.java:329)
        - locked <0x0000000622afa510> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(java.base@17.0.11/BufferedReader.java:396)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:53)

"Notification Thread" #23 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=196.07s tid=0x000001f8fc279220 nid=0xbde4 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"A" #24 prio=5 os_prio=0 cpu=0.00ms elapsed=196.07s tid=0x000001f8fc296e80 nid=0x10308 waiting for monitor entry  [0x0000006af3eff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.atguigu.sync.DeadLock.lambda$main$0(DeadLock.java:24)
        - waiting to lock <0x0000000622b5ee98> (a java.lang.Object)
        - locked <0x0000000622b5ee88> (a java.lang.Object)
        at com.atguigu.sync.DeadLock$$Lambda$14/0x000001f8bc001200.run(Unknown Source)
        at java.lang.Thread.run(java.base@17.0.11/Thread.java:842)

"B" #25 prio=5 os_prio=0 cpu=0.00ms elapsed=196.07s tid=0x000001f8fc29c1b0 nid=0xf378 waiting for monitor entry  [0x0000006af3ffe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.atguigu.sync.DeadLock.lambda$main$1(DeadLock.java:38)
        - waiting to lock <0x0000000622b5ee88> (a java.lang.Object)
        - locked <0x0000000622b5ee98> (a java.lang.Object)
        at com.atguigu.sync.DeadLock$$Lambda$15/0x000001f8bc001418.run(Unknown Source)
        at java.lang.Thread.run(java.base@17.0.11/Thread.java:842)

"DestroyJavaVM" #26 prio=5 os_prio=0 cpu=0.00ms elapsed=196.07s tid=0x000001f88d7a7660 nid=0x5df0 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"VM Thread" os_prio=2 cpu=0.00ms elapsed=196.10s tid=0x000001f8b9f813d0 nid=0x1157c runnable

"GC Thread#0" os_prio=2 cpu=0.00ms elapsed=196.12s tid=0x000001f88d854e60 nid=0xe388 runnable

"G1 Main Marker" os_prio=2 cpu=0.00ms elapsed=196.11s tid=0x000001f88d865c40 nid=0x111a8 runnable

"G1 Conc#0" os_prio=2 cpu=0.00ms elapsed=196.11s tid=0x000001f88d866650 nid=0x8228 runnable

"G1 Refine#0" os_prio=2 cpu=0.00ms elapsed=196.11s tid=0x000001f8b9ebd4d0 nid=0x4888 runnable

"G1 Service" os_prio=2 cpu=0.00ms elapsed=196.11s tid=0x000001f8b9ebdf00 nid=0x80b4 runnable

"VM Periodic Task Thread" os_prio=2 cpu=0.00ms elapsed=196.07s tid=0x000001f8b9ddc390 nid=0x11520 waiting on condition

JNI global refs: 23, weak refs: 0


Found one Java-level deadlock:
=============================
"A":
  waiting to lock monitor 0x000001f8fc29f130 (object 0x0000000622b5ee98, a java.lang.Object),
  which is held by "B"

"B":
  waiting to lock monitor 0x000001f8fc2a00f0 (object 0x0000000622b5ee88, a java.lang.Object),
  which is held by "A"

Java stack information for the threads listed above:
===================================================
"A":
        at com.atguigu.sync.DeadLock.lambda$main$0(DeadLock.java:24)
        - waiting to lock <0x0000000622b5ee98> (a java.lang.Object)
        - locked <0x0000000622b5ee88> (a java.lang.Object)
        at com.atguigu.sync.DeadLock$$Lambda$14/0x000001f8bc001200.run(Unknown Source)
        at java.lang.Thread.run(java.base@17.0.11/Thread.java:842)
"B":
        at com.atguigu.sync.DeadLock.lambda$main$1(DeadLock.java:38)
        - waiting to lock <0x0000000622b5ee88> (a java.lang.Object)
        - locked <0x0000000622b5ee98> (a java.lang.Object)
        at com.atguigu.sync.DeadLock$$Lambda$15/0x000001f8bc001418.run(Unknown Source)
        at java.lang.Thread.run(java.base@17.0.11/Thread.java:842)

Found 1 deadlock.

从 jstack 打印的日志可以看出,发生了死锁


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 george_95@126.com