• 对公平锁、非公平锁、可重入锁、递归锁、自旋锁的理解


     本篇文章主要是记录自己的学习笔记,主要内容是:公平锁、非公平锁、可重入锁、递归锁、自旋锁的理解,并实现一个自旋锁。

    公平和非公平锁

    (1)公平锁和非公平锁是什么?

    公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。

    非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的循序,有可能后申请的线程比先申请的线程优先获取锁。但是,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。

    (2)公平锁和非公平锁的区别是什么?

    并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁。

    公平锁和非公平锁两者的区别:

      公平锁:线程按照他们申请锁的顺序获取锁,公平锁就是很公平,在并发环境下,每个线程在获取锁时会先查看此锁维护的等待队列,如果队列为空,获取当前线程时等待队列的第一个,就占有锁。否则,就会加入到等待队列中,以后会按照FIFO的规则从队列中获取锁。

      非公平锁:非公平所比较粗鲁,上来就尝试占有锁,如果尝试失败,就采用类似公平锁的方式获取锁。

    补充:ReentrantLock即使通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点就是吞吐量比公平锁大。同样的synchronized也是一种非公平锁。

    可重入锁(又名递归锁)

    可重入锁定义:指的是同一线程外层函数获得锁忠厚,内层递归函数仍能获取该锁。在同一个线程在外层党法获取锁的时候,在进入内层方法会自动获取锁。也就是说。线程可以进入任何一个它已经拥有的锁所同步着的代码块。

    ReetrantLock和synchronized就是一个典型的可重入锁,其最大的作用就是避免死锁

    下面我们用代码来进行验证:

    class Phone{
        public synchronized void sendMessage(){
            System.out.println(Thread.currentThread().getId() + " 	 " + " invoked sendMessage");
            sendEmail();
        }
    
        public synchronized void sendEmail(){
            System.out.println(Thread.currentThread().getId() + " 	 " + " ###### invoked sendEmail");
            System.out.println("----------------------------------- 分割线  -----------------------------------");
            System.out.println();
        }
    }
    public class ReetrantLockDemo {
    
        public static void main(String[] args) {
    
            Phone phone = new Phone();
            new Thread(()->{
                phone.sendMessage();
            }).start();
    
            new Thread(()->{
                phone.sendMessage();
            }).start();
        }
    }

     输出结果:

    对于RetrantLock同样可得上述结果:

    class Phone{
    
        Lock lock = new ReentrantLock();
    
        public void sendMessage(){
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getId() + " 	 " + " invoked sendMessage");
                sendEmail();
            }finally {
                lock.unlock();
            }
    
        }
    
        public synchronized void sendEmail(){
            try {
                lock.lock();
    
                System.out.println(Thread.currentThread().getId() + " 	 " + " ###### invoked sendEmail");
                System.out.println("----------------------------------- 分割线  -----------------------------------");
                System.out.println();
            }finally {
                lock.unlock();
            }
    
        }
    }
    public class ReetrantLockDemo {
    
        public static void main(String[] args) throws InterruptedException {
    
            Phone phone = new Phone();
    
            new Thread(()->{
                phone.sendMessage();
            }).start();
    
            new Thread(()->{
                phone.sendMessage();
            }).start();
        }
    }

     输出结果同上。

    自旋锁(spinlock)

    自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是:循环会消耗CPU资源。

    我们学习过的CAS原理,就是采用了自旋锁的思想:

     了解了自旋锁的原理之后,我们自己实现一个自旋锁。

    public class SpinLockDemo {
    
        AtomicReference<Thread> atomicReference = new AtomicReference<>();
        public void myLock(){
            System.out.println(Thread.currentThread().getName() + "	 come in");
            Thread thread = Thread.currentThread();
            while (!atomicReference.compareAndSet(null, thread)){
    
            }
        }
    
        public void myUnLock(){
            Thread thread = Thread.currentThread();
            atomicReference.compareAndSet(thread, null);
            System.out.println(Thread.currentThread().getName() + "	 leave out");
        }
        public static void main(String[] args) {
            SpinLockDemo demo = new SpinLockDemo();
    
            new Thread(()->{
                demo.myLock();
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                demo.myUnLock();
            }, "AAA").start();
    
            new Thread(()->{
                demo.myLock();
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                demo.myUnLock();
            }, "BBB").start();
        }
    }

     输出结果:

     

    独占锁(写锁)/共享锁(读锁)/互斥锁

    独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和synchronized来说,它们都是独占锁。

    共享锁:指该锁可以被多个线程所持有。

    对ReentrantReadWriteLock来说,读锁是共享锁,写锁是独占锁。

    读锁的共享锁可以保证并发读是非常高效的,读写,写读,写写的过程是互斥的。

      也就是说,多个线程同时读取一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是,如果有一个线程想去写共享资源,就不应该又其他的线程对资源进行读或者写。

    总结:读-读可共存

         读-写不可以共存

       写-写不可以共存

    代码实现小例子:

    class CacheResource{
        private volatile Map<String, Object> map = new HashMap<>();
        private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    
        public void put(String key, Object value){
            try {
                reentrantReadWriteLock.writeLock().lock();
                System.out.println("--------------------------------------------------------------");
                System.out.println(Thread.currentThread().getId() + " 	 " + "正在写入" + key);
    
            }finally {
                System.out.println(Thread.currentThread().getId() + " 	 " + "写入成功" + key);
                System.out.println("--------------------------------------------------------------");
    
                reentrantReadWriteLock.writeLock().unlock();
            }
        }
    
        public void get(String key){
            try {
                reentrantReadWriteLock.readLock().lock();
                System.out.println(Thread.currentThread().getId() + " 	 " + "正在读取 	" + key);
            }finally {
                System.out.println(Thread.currentThread().getId() + " 	 " + "读取成功 	" + key);
                reentrantReadWriteLock.readLock().unlock();
            }
    
        }
    }
    public class ReentrantReadWriteLockDemo {
    
        public static void main(String[] args) {
            CacheResource resource = new CacheResource();
    
            for (int i = 0; i < 5; i++){
                final int tmp = i;
                new Thread(()->{
                    resource.put(tmp + "", "");
                }).start();
            }
    
            for (int i = 0; i < 5; i++){
                final int tmp = i;
                new Thread(()->{
                    resource.get(tmp + "");
                }).start();
            }
        }
    }

    输出结果:

    --------------------------------------------------------------
    12      正在写入1
    12      写入成功1
    --------------------------------------------------------------
    --------------------------------------------------------------
    11      正在写入0
    11      写入成功0
    --------------------------------------------------------------
    --------------------------------------------------------------
    13      正在写入2
    13      写入成功2
    --------------------------------------------------------------
    --------------------------------------------------------------
    14      正在写入3
    14      写入成功3
    --------------------------------------------------------------
    --------------------------------------------------------------
    15      正在写入4
    15      写入成功4
    --------------------------------------------------------------
    16      正在读取     0
    16      读取成功     0
    18      正在读取     2
    18      读取成功     2
    19      正在读取     3
    19      读取成功     3
    20      正在读取     4
    20      读取成功     4
    17      正在读取     1
    17      读取成功     1
    
    Process finished with exit code 0

     到这里,本篇文章就结束了,虽然写博客会花费一定的时间,但是可以加深自己对知识点的理解,便于日后复习,要坚持。

  • 相关阅读:
    剑指Offer--反转链表
    剑指Offer--链表中倒数第k个结点
    面向对象的六原则一法则
    常见错误汇总
    记人生第一次CF体验
    Game of Credit Cards
    Shell Game (模拟)
    数列分块入门 1 LibreOJ
    范德蒙恒等式
    C. Vasya and String (尺取法)
  • 原文地址:https://www.cnblogs.com/ch-forever/p/10779699.html
Copyright © 2020-2023  润新知