• java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁


    一、公平锁与非公平锁

    1.1 概述

           公平锁:是指多个线程按照申请锁的顺序来获取锁。
            非公平锁:是指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象。饥饿现象就是低优先级的线程可能一直拿不到锁,而一直处于等待状态。

    1.2 区别

           公平锁:Threads acquire a fair lock in the order in which they requested it.
            公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照 FIFO 的规则从队列中取到自己。
            非公平锁:a nonfair lock permits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lock happens to be available when it is requested.
            非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。而且,非公平锁比公平锁的吞吐量大。

    1.3 Java 中的一些公平锁和非公平锁

           1. java 中的 ReentrantLock,默认是非公平锁,当参数 fair 为 true 时,就是公平锁。

      1 /**
      2  * Creates an instance of {@code ReentrantLock}.
      3  * This is equivalent to using {@code ReentrantLock(false)}.
      4  */
      5 public ReentrantLock() {
      6     sync = new NonfairSync();
      7 }
      8 
      9 /**
     10  * Creates an instance of {@code ReentrantLock} with the
     11  * given fairness policy.
     12  *
     13  * @param fair {@code true} if this lock should use a fair ordering policy
     14  */
     15 public ReentrantLock(boolean fair) {
     16     sync = fair ? new FairSync() : new NonfairSync();
     17 }

           2. synchronized 也是一种非公平锁。

    二、可重入锁与不可重入锁

    2.1 概述

           可重入锁(也叫做递归锁): 指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,也就是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。可重入锁最大的作用就是避免死锁。

           不可重入锁,即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞。

    2.2 java 中的可重入锁

    2.2.1 synchronized 锁

      1 class Phone {
      2     public synchronized void sendSMS() {
      3         System.out.println(Thread.currentThread().getName() + " send SMS...");
      4         sendEmail();
      5     }
      6 
      7     public synchronized void sendEmail() {
      8         System.out.println(Thread.currentThread().getName() + " send email...");
      9     }
     10 }
     11 
     12 public class ReentrantLockDemo {
     13 
     14     public static void main(String[] args) {
     15         Phone phone = new Phone();
     16 
     17         new Thread(() -> {
     18             phone.sendSMS();
     19         }, "Thread1").start();
     20 
     21         new Thread(() -> {
     22             phone.sendSMS();
     23         }, "Thread2").start();
     24     }
     25 }
    image

    2.2.2 ReentrantLock

      1 class Phone implements Runnable {
      2     Lock lock = new ReentrantLock();
      3 
      4     @Override
      5     public void run() {
      6         get();
      7     }
      8 
      9     public void get() {
     10         lock.lock();
     11         try {
     12             System.out.println(Thread.currentThread().getName() + " get method...");
     13             set();
     14         } finally {
     15             lock.unlock();
     16         }
     17     }
     18 
     19     public void set() {
     20         lock.lock();
     21         try {
     22             System.out.println(Thread.currentThread().getName() + " set method...");
     23         } finally {
     24             lock.unlock();
     25         }
     26     }
     27 }
     28 
     29 public class ReentrantLockDemo {
     30 
     31     public static void main(String[] args) {
     32         Phone phone = new Phone();
     33 
     34         Thread thread3 = new Thread(phone, "Thread3");
     35         Thread thread4 = new Thread(phone, "Thread4");
     36         thread3.start();
     37         thread4.start();
     38     }
     39 }

    image

    2.3 面试题

          使用 ReentrantLock 时,如果加入两层锁呢,程序是直接报编译错误,还是正常运行,正常运行的话,能得到预期的结果吗?

      1 class Phone implements Runnable {
      2 
      3     // ...
      4 
      5     public void get() {
      6         lock.lock();
      7         lock.lock();
      8         try {
      9             System.out.println(Thread.currentThread().getName() + " get method...");
     10             set();
     11         } finally {
     12             lock.unlock();
     13             lock.unlock();
     14         }
     15     }
     16 
     17     // ...
     18 }

    image

           当缺少 unlock() 时(也就是,lock 和 unlock不是一一对应,lock 比 unlock 多 ),程序不会报编译错误,但得不到预期的结果,从下面可以看出,程序一直处于运行的状态:

    image

           当缺少 lock() 时(也就是,unlock 比 lock 多 ),此时,程序也不会报编译错误,控制台也输出了结果,但是抛出了 IllegalMonitorStateException 异常。

    image

    三、自旋锁

    3.1 概述

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

    3.2 java 中的自旋锁

      1 // Unsafe.java
      2 public final int getAndAddInt(Object var1, long var2, int var4) {
      3     int var5;
      4     do {
      5         var5 = this.getIntVolatile(var1, var2);
      6     } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
      7 
      8     return var5;
      9 }

    3.3 手写一个自旋锁

      1 public class SpinLockDemo {
      2 
      3     AtomicReference<Thread> atomicReference = new AtomicReference<>();
      4 
      5     public void myLock() {
      6         Thread thread = Thread.currentThread();
      7         System.out.println(thread.getName() + " come in...");
      8         while (!atomicReference.compareAndSet(null, thread)) {
      9 
     10         }
     11     }
     12 
     13     public void myUnLock() {
     14         Thread thread = Thread.currentThread();
     15         atomicReference.compareAndSet(thread, null);
     16         System.out.println(thread.getName() + " come out...");
     17     }
     18 
     19     public static void main(String[] args) {
     20 
     21         SpinLockDemo spinLockDemo = new SpinLockDemo();
     22 
     23         new Thread(() -> {
     24             spinLockDemo.myLock();
     25             try {
     26                 TimeUnit.SECONDS.sleep(5);
     27             } catch (InterruptedException e) {
     28                 e.printStackTrace();
     29             }
     30             spinLockDemo.myUnLock();
     31         }, "Thread1").start();
     32 
     33         try {
     34             TimeUnit.SECONDS.sleep(1);
     35         } catch (InterruptedException e) {
     36             e.printStackTrace();
     37         }
     38 
     39         new Thread(() -> {
     40             spinLockDemo.myLock();
     41             try {
     42                 TimeUnit.SECONDS.sleep(1);
     43             } catch (InterruptedException e) {
     44                 e.printStackTrace();
     45             }
     46             spinLockDemo.myUnLock();
     47         }, "Thread2").start();
     48     }
     49 }

    四、写锁(独占锁)、读锁(共享锁)和互斥锁

    4.1 概述

           独占锁:指该锁一次只能被一个线程所持有。对 ReentrantLock 和 Synchronized 而言都是独占锁。
            共享锁:指该锁可被多个线程所持有。
            对 ReentrantReadWriteLock 其读锁是共享锁,其写锁是独占锁。
            读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。

    4.2 示例(模拟缓存)

    4.2.1 加锁前:

           数据写入的时候,被打断:

      1 class MyCache {
      2 
      3     private volatile Map<String, Object> map = new HashMap<>();
      4 
      5     public void put(String key, Object value) {
      6         System.out.println(Thread.currentThread().getName() + " 正在写入:" + key);
      7         try {
      8             TimeUnit.MILLISECONDS.sleep(300);
      9         } catch (InterruptedException e) {
     10             e.printStackTrace();
     11         }
     12         map.put(key, value);
     13         System.out.println(Thread.currentThread().getName() + " 写入完成");
     14     }
     15 
     16     public void get(String key) {
     17         System.out.println(Thread.currentThread().getName() + " 正在读取");
     18         try {
     19             TimeUnit.MILLISECONDS.sleep(300);
     20         } catch (InterruptedException e) {
     21             e.printStackTrace();
     22         }
     23         Object result = map.get(key);
     24         System.out.println(Thread.currentThread().getName() + " 读取完成:" + result);
     25     }
     26 }
     27 
     28 public class ReadWriteLockDemo {
     29 
     30     public static void main(String[] args) {
     31         MyCache myCache = new MyCache();
     32 
     33         for (int i = 1; i <= 5; i++) {
     34             final int temp = i;
     35             new Thread(() -> {
     36                 myCache.put(temp + "", temp + "");
     37             }, String.valueOf(i)).start();
     38         }
     39 
     40         for (int i = 1; i <= 5; i++) {
     41             final int temp = i;
     42             new Thread(() -> {
     43                 myCache.get(temp + "");
     44             }, String.valueOf(i)).start();
     45         }
     46     }
     47 }
    image

    4.2.2 加锁后:

           写入时正常,不会中断;读取时,可以共享锁。

      1 class MyCache {
      2 
      3     private volatile Map<String, Object> map = new HashMap<>();
      4     private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
      5 
      6     public void put(String key, Object value) {
      7         rwLock.writeLock().lock();
      8         try {
      9             System.out.println(Thread.currentThread().getName() + " 正在写入:" + key);
     10             try {
     11                 TimeUnit.MILLISECONDS.sleep(300);
     12             } catch (InterruptedException e) {
     13                 e.printStackTrace();
     14             }
     15             map.put(key, value);
     16             System.out.println(Thread.currentThread().getName() + " 写入完成");
     17         } catch (Exception e) {
     18             e.printStackTrace();
     19         } finally {
     20             rwLock.writeLock().unlock();
     21         }
     22     }
     23 
     24     public void get(String key) {
     25         rwLock.readLock().lock();
     26         try {
     27             System.out.println(Thread.currentThread().getName() + " 正在读取");
     28             try {
     29                 TimeUnit.MILLISECONDS.sleep(300);
     30             } catch (InterruptedException e) {
     31                 e.printStackTrace();
     32             }
     33             Object result = map.get(key);
     34             System.out.println(Thread.currentThread().getName() + " 读取完成:" + result);
     35         } catch (Exception e) {
     36             e.printStackTrace();
     37         } finally {
     38             rwLock.readLock().unlock();
     39         }
     40     }
     41 }
    image

  • 相关阅读:
    js设计模式-工厂模式(XHR工厂)
    js设计模式-工厂模式(抽象工厂)
    javascript设计模式-工厂模式(简单工厂)
    转载【梦想天空(山边小溪)】Web 开发人员和设计师必读文章推荐【系列二十九】
    转载【H:JL】用大家的力量来总结一个目录(众人拾柴火焰高)
    javascript设计模式-单体模式
    javascript设计模式-掺元类
    javascript设计模式-继承
    开博第一天
    iOS开发 在某个视图控制器中 隐藏 状态栏
  • 原文地址:https://www.cnblogs.com/lveyHang/p/11898520.html
Copyright © 2020-2023  润新知