美文网首页
多线程与并发(四):ReentrantLock

多线程与并发(四):ReentrantLock

作者: lilykeke | 来源:发表于2021-09-07 11:36 被阅读0次

    1. 一个例子

    一间大屋子有两个功能:睡觉 学习 ,互不干涉
    现在小男要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低。解决方法是准备多个房间(多个对像锁)

    public class Test1 {
    
        public static void main(String[] args) {
            BigRoom bigRoom = new BigRoom();
            
            new Thread(()->{
                bigRoom.study();
            },"小男").start();
    
            new Thread(()->{
                bigRoom.sleep();
            },"小女").start();
        }
    }
    
    class BigRoom{
        public void study(){
            synchronized (this){
                System.out.println("学习一小时");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public void sleep(){
            synchronized (this){
                System.out.println("睡觉一小时");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    改进

    public class Test1 {
    
        public static void main(String[] args) {
            BigRoom bigRoom = new BigRoom();
    
            new Thread(()->{
                bigRoom.study();
            },"小男").start();
    
            new Thread(()->{
                bigRoom.sleep();
            },"小女").start();
        }
    
    
    }
    
    class BigRoom{
        
        private final Object studyRoom = new Object();
        private final Object sleepRoom = new Object();
        public void study(){
            synchronized (studyRoom){
                System.out.println("学习一小时");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public void sleep(){
            synchronized (sleepRoom){
                System.out.println("睡觉一小时");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    将锁的粒度细分

    • 好处,可以增强并发度
    • 如果一个线程需要同时获取多把锁,就容易发生死锁

    1.1 活跃性

    死锁

    有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁

    t1 线程获得 A对象锁,接下来想获取B对象锁
    t2 线程获得B对象锁,接下来想获取A对象锁

    例如:

    public class Test2 {
    
        public static void main(String[] args) {
            Object A = new Object();
            Object B = new Object();
    
            Thread t1 = new Thread(()->{
                synchronized (A){
                    System.out.println(Thread.currentThread().getName()+"lock A");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (B){
                        System.out.println(Thread.currentThread().getName()+"lock B");
                    }
                }
            },"t1");
    
            Thread t2 = new Thread(()->{
                synchronized (B){
                    System.out.println(Thread.currentThread().getName()+"lock B");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (A){
                        System.out.println(Thread.currentThread().getName()+"lock A");
                    }
                }
            },"t2");
    
            t1.start();
            t2.start();
        }
    }
    

    结果:
    t1lock A
    t2lock B


    定位死锁

    • 检测死锁可以使用jconsole工具,或者使用jps定位进程id,再用jstack 定位死锁。
    ItsBadForYadeMacBook-Pro:lily itsbadforya$ jps
    26273 Launcher
    26275 Test2
    25431 Test
    25194 
    26285 Jps
    26270 KotlinCompileDaemon
    ItsBadForYadeMacBook-Pro:lily itsbadforya$ jstack 26275
    2020-10-05 14:39:51
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.211-b12 mixed mode):
    
    "Attach Listener" #16 daemon prio=9 os_prio=31 tid=0x00007ff49e827000 nid=0x470b waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "DestroyJavaVM" #15 prio=5 os_prio=31 tid=0x00007ff4a1159800 nid=0xf03 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "t2" #14 prio=5 os_prio=31 tid=0x00007ff4a08d6800 nid=0xa403 waiting for monitor entry [0x0000700006a75000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at Thread.Test2.lambda$main$1(Test2.java:32)
            - waiting to lock <0x00000007957dab48> (a java.lang.Object)
            - locked <0x00000007957dab58> (a java.lang.Object)
            at Thread.Test2$$Lambda$2/1879492184.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)
    
    "t1" #13 prio=5 os_prio=31 tid=0x00007ff4a0846000 nid=0x5703 waiting for monitor entry [0x0000700006972000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at Thread.Test2.lambda$main$0(Test2.java:18)
            - waiting to lock <0x00000007957dab58> (a java.lang.Object)
            - locked <0x00000007957dab48> (a java.lang.Object)
            at Thread.Test2$$Lambda$1/1674896058.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)
    
    "Service Thread" #12 daemon prio=9 os_prio=31 tid=0x00007ff49e03f000 nid=0xa803 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C1 CompilerThread3" #11 daemon prio=9 os_prio=31 tid=0x00007ff4a086f000 nid=0x5503 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread2" #10 daemon prio=9 os_prio=31 tid=0x00007ff4a086e000 nid=0x3c03 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread1" #9 daemon prio=9 os_prio=31 tid=0x00007ff4a086d000 nid=0x3d03 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread0" #8 daemon prio=9 os_prio=31 tid=0x00007ff4a086c800 nid=0x3903 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "JDWP Command Reader" #7 daemon prio=10 os_prio=31 tid=0x00007ff4a380b800 nid=0x3803 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "JDWP Event Helper Thread" #6 daemon prio=10 os_prio=31 tid=0x00007ff49e817800 nid=0x4003 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "JDWP Transport Listener: dt_socket" #5 daemon prio=10 os_prio=31 tid=0x00007ff49e816800 nid=0x3607 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007ff49e810000 nid=0x3503 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007ff4a3808800 nid=0x2f03 in Object.wait() [0x0000700005d4b000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x0000000795588ed0> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
            - locked <0x0000000795588ed0> (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=31 tid=0x00007ff4a0826800 nid=0x4d03 in Object.wait() [0x0000700005c48000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x0000000795586bf8> (a java.lang.ref.Reference$Lock)
            at java.lang.Object.wait(Object.java:502)
            at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
            - locked <0x0000000795586bf8> (a java.lang.ref.Reference$Lock)
            at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
    
    "VM Thread" os_prio=31 tid=0x00007ff4a081f800 nid=0x4e03 runnable 
    
    "GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007ff49e00d000 nid=0x2307 runnable 
    
    "GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007ff49e010000 nid=0x2103 runnable 
    
    "GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007ff49e010800 nid=0x1e03 runnable 
    
    "GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007ff49e011800 nid=0x2a03 runnable 
    
    "GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007ff49e012000 nid=0x5403 runnable 
    
    "GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007ff49e012800 nid=0x2d03 runnable 
    
    "GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007ff49e013000 nid=0x5203 runnable 
    
    "GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007ff49e014000 nid=0x5003 runnable 
    
    "VM Periodic Task Thread" os_prio=31 tid=0x00007ff4a38b8000 nid=0x5603 waiting on condition 
    
    JNI global references: 2246
    
    
    Found one Java-level deadlock:
    =============================
    "t2":
      waiting to lock monitor 0x00007ff4a082c4a8 (object 0x00000007957dab48, a java.lang.Object),
      which is held by "t1"
    "t1":
      waiting to lock monitor 0x00007ff4a0828408 (object 0x00000007957dab58, a java.lang.Object),
      which is held by "t2"
    
    Java stack information for the threads listed above:
    ===================================================
    "t2":
            at Thread.Test2.lambda$main$1(Test2.java:32)
            - waiting to lock <0x00000007957dab48> (a java.lang.Object)
            - locked <0x00000007957dab58> (a java.lang.Object)
            at Thread.Test2$$Lambda$2/1879492184.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)
    "t1":
            at Thread.Test2.lambda$main$0(Test2.java:18)
            - waiting to lock <0x00000007957dab58> (a java.lang.Object)
            - locked <0x00000007957dab48> (a java.lang.Object)
            at Thread.Test2$$Lambda$1/1674896058.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)
    
    Found 1 deadlock.
    
    ItsBadForYadeMacBook-Pro:lily itsbadforya$ 
    
    

    哲学家就餐问题

    有五位哲学家,围坐在圆桌旁

    • 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完后继续思考

    • 吃饭时要用两根筷子吃,桌子上共有五根筷子,每位哲学家左右手边各有一根筷子

    • 如果筷子被身边的人拿着,自己就得等着

    • 代码

    public class Test3 {
    
    
        public static void main(String[] args) {
            //创建五根筷子
    
            Chopstick c1 = new Chopstick("1");
            Chopstick c2 = new Chopstick("2");
            Chopstick c3 = new Chopstick("3");
            Chopstick c4 = new Chopstick("4");
            Chopstick c5 = new Chopstick("5");
    
            //创建五个哲学家
            new Philosopher("苏格拉底",c1,c2).start();
            new Philosopher("柏拉图",c2,c3).start();
            new Philosopher("亚里士多德",c3,c4).start();
            new Philosopher("赫拉克利特",c4,c5).start();
            new Philosopher("阿基米德",c5,c1).start();
    
        }
    
    }
    
    class Philosopher extends Thread{
        Chopstick left;
        Chopstick right;
    
    
        public Philosopher(String name, Chopstick left,Chopstick right){
            super(name);
            this.left = left;
            this.right = right;
        }
    
        @Override
        public void run() {
            while(true){
                //尝试获得左手的筷子
                synchronized (left){
                    //尝试获得右手的筷子
                    synchronized (right){
                        eat();
                    }
                }
            }
        }
    
    
        private void eat(){
            System.out.println(Thread.currentThread().getName()+"eating...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    class Chopstick{
        String name;
    
    
        public Chopstick(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Chopstick{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    

    执行一段时间后出现死锁,问题定位:

    ItsBadForYadeMacBook-Pro:lily itsbadforya$ jstack 26610
    2020-10-05 20:02:07
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.211-b12 mixed mode):
    
    "Attach Listener" #19 daemon prio=9 os_prio=31 tid=0x00007f868d027000 nid=0x3707 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "DestroyJavaVM" #18 prio=5 os_prio=31 tid=0x00007f868d812000 nid=0x1003 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "阿基米德" #17 prio=5 os_prio=31 tid=0x00007f868c048000 nid=0xa103 waiting for monitor entry [0x00007000062fd000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at Thread.Philosopher.run(Test3.java:44)
            - waiting to lock <0x00000007957dc120> (a Thread.Chopstick)
            - locked <0x00000007957dc190> (a Thread.Chopstick)
    
    "赫拉克利特" #16 prio=5 os_prio=31 tid=0x00007f868d811000 nid=0x5a03 waiting for monitor entry [0x00007000061fa000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at Thread.Philosopher.run(Test3.java:44)
            - waiting to lock <0x00000007957dc190> (a Thread.Chopstick)
            - locked <0x00000007957dc180> (a Thread.Chopstick)
    
    "亚里士多德" #15 prio=5 os_prio=31 tid=0x00007f8689839000 nid=0xa403 waiting for monitor entry [0x00007000060f7000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at Thread.Philosopher.run(Test3.java:44)
            - waiting to lock <0x00000007957dc180> (a Thread.Chopstick)
            - locked <0x00000007957dc170> (a Thread.Chopstick)
    
    "柏拉图" #14 prio=5 os_prio=31 tid=0x00007f8688851000 nid=0xa503 waiting for monitor entry [0x0000700005ff4000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at Thread.Philosopher.run(Test3.java:44)
            - waiting to lock <0x00000007957dc170> (a Thread.Chopstick)
            - locked <0x00000007957dc160> (a Thread.Chopstick)
    
    "苏格拉底" #13 prio=5 os_prio=31 tid=0x00007f8688850800 nid=0xa603 waiting for monitor entry [0x0000700005ef1000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at Thread.Philosopher.run(Test3.java:44)
            - waiting to lock <0x00000007957dc160> (a Thread.Chopstick)
            - locked <0x00000007957dc120> (a Thread.Chopstick)
    
    "Service Thread" #12 daemon prio=9 os_prio=31 tid=0x00007f868784d000 nid=0xa903 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C1 CompilerThread3" #11 daemon prio=9 os_prio=31 tid=0x00007f868d808800 nid=0x5503 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread2" #10 daemon prio=9 os_prio=31 tid=0x00007f8688852000 nid=0x4103 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread1" #9 daemon prio=9 os_prio=31 tid=0x00007f868884f800 nid=0x3f03 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread0" #8 daemon prio=9 os_prio=31 tid=0x00007f868884f000 nid=0x3d03 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "JDWP Command Reader" #7 daemon prio=10 os_prio=31 tid=0x00007f8689833000 nid=0x3c03 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "JDWP Event Helper Thread" #6 daemon prio=10 os_prio=31 tid=0x00007f868d008800 nid=0x4503 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "JDWP Transport Listener: dt_socket" #5 daemon prio=10 os_prio=31 tid=0x00007f8687808800 nid=0x4707 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007f868801a000 nid=0x4803 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007f8689830800 nid=0x4f03 in Object.wait() [0x00007000052ca000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x0000000795588ed0> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
            - locked <0x0000000795588ed0> (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=31 tid=0x00007f8688812800 nid=0x5103 in Object.wait() [0x00007000051c7000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x0000000795586bf8> (a java.lang.ref.Reference$Lock)
            at java.lang.Object.wait(Object.java:502)
            at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
            - locked <0x0000000795586bf8> (a java.lang.ref.Reference$Lock)
            at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
    
    "VM Thread" os_prio=31 tid=0x00007f868982f800 nid=0x5303 runnable 
    
    "GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007f868800b800 nid=0x2307 runnable 
    
    "GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007f868800f000 nid=0x2103 runnable 
    
    "GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007f8688010000 nid=0x1e03 runnable 
    
    "GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007f868880a800 nid=0x2a03 runnable 
    
    "GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007f8688010800 nid=0x2c03 runnable 
    
    "GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007f868880b800 nid=0x2e03 runnable 
    
    "GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007f8688011000 nid=0x3003 runnable 
    
    "GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007f8688011800 nid=0x5403 runnable 
    
    "VM Periodic Task Thread" os_prio=31 tid=0x00007f868c00b000 nid=0x5703 waiting on condition 
    
    JNI global references: 1441
    
    
    Found one Java-level deadlock:
    =============================
    "阿基米德":
      waiting to lock monitor 0x00007f86888540b8 (object 0x00000007957dc120, a Thread.Chopstick),
      which is held by "苏格拉底"
    "苏格拉底":
      waiting to lock monitor 0x00007f86880177f8 (object 0x00000007957dc160, a Thread.Chopstick),
      which is held by "柏拉图"
    "柏拉图":
      waiting to lock monitor 0x00007f8688017748 (object 0x00000007957dc170, a Thread.Chopstick),
      which is held by "亚里士多德"
    "亚里士多德":
      waiting to lock monitor 0x00007f8688854008 (object 0x00000007957dc180, a Thread.Chopstick),
      which is held by "赫拉克利特"
    "赫拉克利特":
      waiting to lock monitor 0x00007f8688853f58 (object 0x00000007957dc190, a Thread.Chopstick),
      which is held by "阿基米德"
    
    Java stack information for the threads listed above:
    ===================================================
    "阿基米德":
            at Thread.Philosopher.run(Test3.java:44)
            - waiting to lock <0x00000007957dc120> (a Thread.Chopstick)
            - locked <0x00000007957dc190> (a Thread.Chopstick)
    "苏格拉底":
            at Thread.Philosopher.run(Test3.java:44)
            - waiting to lock <0x00000007957dc160> (a Thread.Chopstick)
            - locked <0x00000007957dc120> (a Thread.Chopstick)
    "柏拉图":
            at Thread.Philosopher.run(Test3.java:44)
            - waiting to lock <0x00000007957dc170> (a Thread.Chopstick)
            - locked <0x00000007957dc160> (a Thread.Chopstick)
    "亚里士多德":
            at Thread.Philosopher.run(Test3.java:44)
            - waiting to lock <0x00000007957dc180> (a Thread.Chopstick)
            - locked <0x00000007957dc170> (a Thread.Chopstick)
    "赫拉克利特":
            at Thread.Philosopher.run(Test3.java:44)
            - waiting to lock <0x00000007957dc190> (a Thread.Chopstick)
            - locked <0x00000007957dc180> (a Thread.Chopstick)
    
    Found 1 deadlock.
    

    活锁

    活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如:

    public class Test4 {
        static volatile int count = 10;
        static final Object lock = new Object();
    
        public static void main(String[] args) {
            new Thread(() -> {
                //期望减到0退出循环
    
                while (count > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    count--;
                    System.out.println(Thread.currentThread().getName() + "current count is " + count);
                }
            }, "t1").start();
    
            new Thread(() -> {
                //期望超过20退出循环
    
                while (count < 20) {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    count++;
                    System.out.println(Thread.currentThread().getName() + "current count is " + count);
                }
            }, "t2").start();
        }
    }
    

    执行结果:不能停止
    解决方法:睡眠的时间要不一样

    饥饿

    很多线程中把饥饿定义为,一个线程由于优先级太低,始终得不到CPU调度,也不能够结束,饥饿的情况不易演示,这样读写锁时会涉及饥饿的问题。

    下面我讲一个我遇到的线程饥饿的例子,先来看看使用顺序加锁的方式解决之前死锁的问题


    死锁.jpg

    顺序加锁的解决方案


    死锁-2.jpg

    没有死锁,但是有可能会造成线程饥饿,得不到执行

    2. ReentrantLock

    相对于synchronized 它具备如下特点:

    • 可中断
    • 可以设置超时时间
    • 可以设置为公平锁
    • 支持多个条件变量
      与synchronized 一样都支持可重入
      基本语法
    //获取锁
    reentrantLock.lock();
    try{
        //临界区
    }finally{
        //释放锁
        reentrantLock.unlock();
    }
    

    2.1 可重入

    可重入是指同一个线程如果首次获得这把锁,那么因为它是这把锁的拥有者,因此具有权利再次获取这把锁。如果不是可重入锁,那么第二次获得锁的时候,自己会被锁挡住。

    public class Test5 {
    
        static ReentrantLock lock = new ReentrantLock();
    
        public static void main(String[] args) {
            method1();
        }
    
        public static void method1(){
            lock.lock();
    
            try {
                System.out.println("excute method1");
                method2();
            } finally {
                lock.unlock();
            }
        }
    
        public static void method2(){
            lock.lock();
    
            try {
                System.out.println("excute method2");
                method3();
            } finally {
                lock.unlock();
            }
        }
    
        public static void method3(){
            lock.lock();
    
            try {
                System.out.println("excute method3");
    
            } finally {
                lock.unlock();
            }
        }
    }
    

    2.2 可打断

    public class Test5 {
    
        static ReentrantLock lock = new ReentrantLock();
    
        public static void main(String[] args) {
    
            Thread t1 = new Thread(()->{
                try {
                    //如果没有竞争那么此方法就会获取lock对象锁
                    //如果有竞争就进入阻塞队列,可以被其它线程用interrupt()方法打断
                    System.out.println("尝试获得锁");
                    lock.lockInterruptibly();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("没有获得锁,返回");
                    return;
                }
    
                try {
                    System.out.println("获取到锁");
                } finally {
                    lock.unlock();
                }
            },"t1");
    
            lock.lock();
    
            t1.start();
    
    
            try {
                Thread.sleep(1000);
                System.out.println("打断t1");
                t1.interrupt();
            } catch(InterruptedException e) {
    
            }
    
        }
    

    执行结果:

    尝试获得锁
    打断t1
    java.lang.InterruptedException
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
        at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
        at Thread.Test5.lambda$main$0(Test5.java:16)
        at java.lang.Thread.run(Thread.java:748)
    没有获得锁,返回
    

    2.3 锁超时

    立刻失败

    public class Thread5 {
    
        public static void main(String[] args) {
            ReentrantLock lock = new ReentrantLock();
    
            Thread t1 = new Thread(() -> {
                System.out.println("启动");
                System.out.println("尝试获取锁");
                if (!lock.tryLock()) {
                    System.out.println("获取立刻失败,返回");
                    return;
                }
    
                try {
                    System.out.println(Thread.currentThread().getName() + "获得了锁");
                } finally {
                    lock.unlock();
                }
            }, "t1");
    
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "获得了锁");
            t1.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    

    执行结果:
    main获得了锁
    启动
    尝试获取锁
    获取立刻失败,返回


    带参数的tryLock(long n,TimeUnit unit)

    public class Thread5 {
    
        public static void main(String[] args) {
            ReentrantLock lock = new ReentrantLock();
    
            Thread t1 = new Thread(() -> {
                System.out.println("启动");
                System.out.println("尝试获取锁");
                try {
                    if (!lock.tryLock(2, TimeUnit.SECONDS)) {
                        System.out.println("获取立刻失败,返回");
                        return;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                try {
                    System.out.println(Thread.currentThread().getName() + "获得了锁");
                } finally {
                    lock.unlock();
                }
            }, "t1");
    
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "获得了锁");
            t1.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    

    执行结果:
    main获得了锁
    启动
    尝试获取锁
    t1获得了锁


    2.4 锁超时-解决哲学家就餐问题

    public class Test3 {
    
    
        public static void main(String[] args) {
            //创建五根筷子
    
            Chopstick c1 = new Chopstick("1");
            Chopstick c2 = new Chopstick("2");
            Chopstick c3 = new Chopstick("3");
            Chopstick c4 = new Chopstick("4");
            Chopstick c5 = new Chopstick("5");
    
            //创建五个哲学家
            new Philosopher("苏格拉底", c1, c2).start();
            new Philosopher("柏拉图", c2, c3).start();
            new Philosopher("亚里士多德", c3, c4).start();
            new Philosopher("赫拉克利特", c4, c5).start();
            new Philosopher("阿基米德", c5, c1).start();
    
        }
    
    }
    
    class Philosopher extends Thread {
        Chopstick left;
        Chopstick right;
    
    
        public Philosopher(String name, Chopstick left, Chopstick right) {
            super(name);
            this.left = left;
            this.right = right;
        }
    
        @Override
        public void run() {
            while (true) {
                //尝试获得左手的筷子
                if (left.tryLock()) {
                    try{
                        //尝试获得右手的筷子
    
                        if (right.tryLock()){
                            try{
                                eat();
                            }finally {
                                right.unlock();
                            }
                        }
                    }finally {
                        left.unlock();//获取右边筷子失败,释放自己的左手筷子
                    }
                }
    
            }
        }
    
    
        private void eat() {
            System.out.println(Thread.currentThread().getName() + "eating...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    class Chopstick extends ReentrantLock {
        String name;
    
    
        public Chopstick(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Chopstick{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    

    2.5 公平锁

    ReentrantLock默认是不公平的

    • 从源码的角度分析

    公平锁一般没有必要,会降低并发度。

    2.6 条件变量

    synchronized 中也有条件变量,就是我们讲原理的那个waitSet休息室,当条件不满足时进入waitSet等待。
    ReentrantLock的条件变量比synchronized 强大之处在于,它是支持多个条件变量的,这就好比

    • synchronized 是那些不满足条件的线程都在一间休息室中等消息
    • ReentrantLock 支持多间休息室,有专门等烟的休息室,有专门等外卖的休息室。唤醒时也是按休息室来唤醒。

    使用流程

    • await前需要获得锁
    • await 执行后,会释放锁,进入conditionObject等待
    • await 的线程被唤醒(或打断,或超时),重新竞争lock锁
    • 竞争lock锁成功后,从await后继续执行
    public class Thread6 {
        
        static ReentrantLock Room = new ReentrantLock();
        //创建条件变量
        static Condition waitCigaretteQueue = Room.newCondition();
        static Condition waitBreakfastQueue = Room.newCondition();
    
        static volatile boolean hasCigarette;
        static volatile boolean hasBreakfast;
    
        public static void main(String[] args) {
    
            new Thread(() -> {
    
                Room.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "有烟没?" + hasCigarette);
                    while (!hasCigarette) {
                        try {
                            waitCigaretteQueue.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    System.out.println(Thread.currentThread().getName() + "再次问有烟没?" + hasCigarette);
                    if (hasCigarette) {
                        System.out.println(Thread.currentThread().getName() + "可以开始干活了");
                    } else {
                        System.out.println("没干成");
                    }
                } finally {
                    Room.unlock();
                }
    
            }, "小男").start();
    
    
            new Thread(() -> {
    
                Room.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "有早餐没?" + hasBreakfast);
                    while (!hasBreakfast) {
                        try {
                            waitBreakfastQueue.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    System.out.println(Thread.currentThread().getName() + "再次问有早餐没?" + hasBreakfast);
                    if (hasBreakfast) {
                        System.out.println(Thread.currentThread().getName() + "可以开始干活了");
                    } else {
                        System.out.println("没干成");
                    }
                } finally {
                    Room.unlock();
                }
    
            }, "小女").start();
    
            new Thread(() -> {
                Room.lock();
    
                try {
                    System.out.println("送外卖来了");
                    hasBreakfast = true;
                    waitBreakfastQueue.signal();
                } finally {
                    Room.unlock();
                }
            }, "送外卖的").start();
    
            new Thread(() -> {
                Room.lock();
    
                try {
                    System.out.println("送烟来了");
                    hasCigarette = true;
                    waitCigaretteQueue.signal();
                } finally {
                    Room.unlock();
                }
            }, "送烟的").start();
        }
    }
    

    执行结果:
    小男有烟没?false
    小女有早餐没?false
    送外卖来了
    小女再次问有早餐没?true
    小女可以开始干活了
    送烟来了
    小男再次问有烟没?true
    小男可以开始干活了


    同步模式之顺序控制

    比如,必须先2后1打印
    wait-notify

    public class Test6 {
    
        static Object obj = new Object();
        //t2运行标记,代表t2是否执行过
        static boolean t2runed = false;
    
        public static void main(String[] args) {
            Thread t1 = new Thread(()->{
                synchronized (obj){
                    while (!t2runed){
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("打印1");
                }
    
            },"t1");
    
            Thread t2 = new Thread(()->{
                synchronized (obj){
                    System.out.println("打印2");
                    t2runed = true;
                    obj.notify();
                }
            },"t2");
    
            t1.start();
            t2.start();
        }
    }
    

    执行结果:
    打印2
    打印1


    park & unpark解决方式
    public class Test7 {
    
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
    
                LockSupport.park();
                System.out.println("打印1");
    
            }, "t1");
    
            Thread t2 = new Thread(() -> {
    
                System.out.println("打印2");
                LockSupport.unpark(t1);
    
            }, "t2");
    
            t1.start();
            t2.start();
        }
    }
    

    执行结果:
    打印2
    打印1


    交替输出

    线程1 输出a5次,线程2输出b 5次,线程3输出c 5次。要求输出abcabcabcabcabc

    wait - notify版本
    public class Test8 {
    
        public static void main(String[] args) {
            WaitNotify waitNotify = new WaitNotify(1,5);
            new Thread(()->{
                waitNotify.print("a",1,2);
            }).start();
            new Thread(()->{
                waitNotify.print("b",2,3);
            }).start();
            new Thread(()->{
                waitNotify.print("c",3,1);
            }).start();
        }
    }
    
    class WaitNotify{
        //等待标记
        private int flags;
        //循环次数
        private int loopNumber;
    
        public WaitNotify(int flags, int loopNumber){
            this.flags = flags;
            this.loopNumber = loopNumber;
        }
    
        public void print(String str, int waitFlags, int nextFlags){
            for (int i = 0; i < loopNumber; i++) {
                synchronized (this){
                    while (flags!= waitFlags){
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    System.out.print(str);
                    flags = nextFlags;
                    this.notifyAll();
                }
            }
    
        }
    }
    

    ReentrantLock 方式

    public class Test9 {
        public static void main(String[] args) {
            AwaitSignal awaitSignal = new AwaitSignal(5);
            Condition a = awaitSignal.newCondition();
            Condition b = awaitSignal.newCondition();
            Condition c = awaitSignal.newCondition();
    
            new Thread(() -> {
                awaitSignal.print("a", a, b);
            }).start();
            new Thread(() -> {
                awaitSignal.print("b", b, c);
            }).start();
            new Thread(() -> {
                awaitSignal.print("c", c, a);
            }).start();
    
            //所有的线程先进入休息室,然后由主线程唤醒
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            awaitSignal.lock();
            try{
                a.signal();
            }finally {
                awaitSignal.unlock();
            }
    
        }
    }
    
    
    class AwaitSignal extends ReentrantLock {
        private int loopNumber;
    
        public AwaitSignal(int i) {
            loopNumber = i;
        }
    
        //参数一:打印内容,参数二:进入哪一间休息室等待,参数三:下一间休息室
        public void print(String str, Condition current, Condition next) {
            for (int i = 0; i < loopNumber; i++) {
                lock();
                try {
                    current.await();
                    System.out.print(str);
                    next.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    unlock();
                }
            }
        }
    }
    

    park&unpark方式

    public class Test10 {
    
        private static Thread t1, t2, t3;
    
        public static void main(String[] args) {
            ParkUnpark parkUnpark = new ParkUnpark(5);
            t1 = new Thread(() -> {
                parkUnpark.print("a", t2);
            });
            t2 = new Thread(() -> {
                parkUnpark.print("b", t3);
            });
            t3 = new Thread(() -> {
                parkUnpark.print("c", t1);
            });
    
            t1.start();
            t2.start();
            t3.start();
    
            LockSupport.unpark(t1);
        }
    
    }
    
    class ParkUnpark {
    
        private int loopNumber;
    
        public ParkUnpark(int loopNumber) {
            this.loopNumber = loopNumber;
        }
    
        public void print(String str, Thread next) {
            for (int i = 0; i < loopNumber; i++) {
                LockSupport.park();
                System.out.print(str);
    
                LockSupport.unpark(next);
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:多线程与并发(四):ReentrantLock

          本文链接:https://www.haomeiwen.com/subject/vfdauktx.html