美文网首页
072-JAVA线程安全的重入锁方案【阻塞】

072-JAVA线程安全的重入锁方案【阻塞】

作者: XAbo | 来源:发表于2022-06-14 23:18 被阅读0次

    重入锁(ReentrantLock)的引入就是为了解决同步锁存在活跃性的问题。

    不同点

    1. 可重入锁,同个线程可以对同一个锁多次获取。
    2. 可重入锁,可以被打断,需创建可打断的锁lockInterruptibly()
    3. 可重入锁,ReentrantLock默认与同步锁一样是不公平锁,但是可以设置为公平锁,解决同步锁的饥饿,但是意义不大。
    4. 可以创建条件变量,解决同步锁的虚假唤醒。
    5. 可以由程序员加锁和释放锁。
    6. 可以设置锁时长tryLock(),带有时长的锁,也是可以被打断的。

    相同点

    1. 锁都可以被重入。

    一、解决同步锁的活跃性

    1.1 解决死锁

    复现:哲学家就餐问题;synchronized会一致等待锁。

    public class Lock {
        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("阿基米德", c5, c1).start();
            new Philosopher("赫拉克利特", c4, c5).start();
            new Philosopher("亚里士多德", c3, c4).start();
            new Philosopher("柏拉图", c2, c3).start();
            new Philosopher("苏格拉底", c1, c2).start();
        }
    
        static class Philosopher extends Thread {
            Chopstick right;
            Chopstick left;
    
            public Philosopher(String name, Chopstick right, Chopstick left) {
                super(name);
                this.left = left;
                this.right = right;
            }
    
            @Override
            public void run() {
                while (true) {
                    synchronized (left) {
                        synchronized (right) {
                            try {
                                eat();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
            private void eat() throws InterruptedException {
                System.out.println(currentThread().getName() + "吃");
            }
        }
        static class Chopstick {
            String name;
            public Chopstick(String name) {
                this.name = name;
            }
            @Override
            public String toString() {
                return "筷子{" +
                        "name='" + name + '\'' +
                        '}';
            }
        }
    }
    
    结果:死锁
    使用tryLock解决死锁;结果:停不下来。
    public class Lock {
        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("阿基米德", c5, c1).start();
            new Philosopher("赫拉克利特", c4, c5).start();
            new Philosopher("亚里士多德", c3, c4).start();
            new Philosopher("柏拉图", c2, c3).start();
            new Philosopher("苏格拉底", c1, c2).start();
        }
    
        static class Philosopher extends Thread {
            Chopstick right;
            Chopstick left;
            public Philosopher(String name, Chopstick right, Chopstick left) {
                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(currentThread().getName() + "吃");
            }
        }
        static class Chopstick  extends ReentrantLock{
            String name;
            public Chopstick(String name) {
                this.name = name;
            }
            @Override
            public String toString() {
                return "筷子{" +
                        "name='" + name + '\'' +
                        '}';
            }
        }
    }
    

    1.2解决饥饿

    公平锁一般不使用。常见公平锁对象:
    new ReentrantLock(true)、new ReentrantReadWriteLock(true)等。

    1.3 解决虚假唤醒

    public class WaitTest {
        static boolean hasCigarette = false;
        static boolean hasTakeout = false;
        static ReentrantLock ROOM = new ReentrantLock();
        static Condition waitCigaretteSet =  ROOM.newCondition();
        static Condition waitTakeoutSet =  ROOM.newCondition();
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                    ROOM.lock();
                    try {
                        System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有烟吗? " + hasCigarette);
                        while(!hasCigarette){
                            System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":没烟,歇会。" );
                            try {
                                waitCigaretteSet.await();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有烟吗? " + hasCigarette);
                        if(hasCigarette){
                            System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有烟,干活。");
                        }else {
                            System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":接着睡。");
                        }
                    }finally {
                        ROOM.unlock();
                    }
            },"小南").start();
            new Thread(()->{
                ROOM.lock();
                    try {
                        System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有饭吗? " + hasTakeout);
                        while(!hasTakeout){
                            System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":没饭,歇会。" );
                            try {
                                waitTakeoutSet.await();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有饭吗? " + hasTakeout);
                        if(hasTakeout){
                            System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":有饭,干活。");
                        }
                    }finally {
                        ROOM.unlock();
                    }
            },"大南").start();
            Thread.sleep(1000);
            new Thread(()->{
                ROOM.lock();
                try {
                    System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":饭来。");
                    hasTakeout = true;
                    waitTakeoutSet.signal();
                }finally {
                    ROOM.unlock();
                }
            },"小女").start();
            new Thread(()->{
                ROOM.lock();
                try {
                    System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()+":烟来。");
                    hasCigarette = true;
                    waitCigaretteSet.signal();
                }finally {
                    ROOM.unlock();
                }
            },"小女女").start();
            
        }
    }
    
    精准唤醒

    二、可重入锁原理

    java.util .concurrent包中提供了Lock接口,允许自定义一个锁,自定义锁往往利用AbstracQueuedSynchronizer(AQS)来实现。
    Java已经提供一个现成的实现类ReentrantLock,其也是利用AQS工具类实现的。

    继承关系

    2.1 非公平锁的原理

    摘抄:https://blog.csdn.net/qq_37671094/article/details/119760799

    非公平锁的原理

    2.2 可重入的原理

    可重入的加锁 可重入的解锁

    2.3 可打断的原理

    默认不可打断原理:即使被打断,线程仍然会驻留在AQS队列中,等获得锁后继续运行(打断标记被设置true)。

    不可打断的原理 可打断的原理

    2.4 公平锁的原理

    公平锁的原理

    2.5 条件变量的原理

    条件变量实现原理

    三、读写锁原理

    摘抄:https://blog.csdn.net/qq_37671094/article/details/119870104
    对共享资源是修改还是读取都加锁,会损失性能,那么我们可不可以对锁进行细分,使读-读可并发,由此就引出了该文章,读写锁ReentrantReadWriteLock

    读写锁原理

    3.1 StampedLock

    该类自JDK8加入,是为了进一步优化读性能,大的特点是在使用读锁、写锁时配合使用【戳】

    StampedLock的核心

    3.2 Semaphore

    摘抄:https://blog.csdn.net/qq_37671094/article/details/119904124
    信号量,用来限制能同时访问共享资源的线程上限。

    Semaphore原理 CountdownLatch& CyclicBarrier

    相关文章

      网友评论

          本文标题:072-JAVA线程安全的重入锁方案【阻塞】

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