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

原文地址:https://www.cnblogs.com/lveyHang/p/11898520.html