【Java锁】(公平锁、非公平锁、可重入锁、递归锁、自旋锁)谈谈你的理解?手写一个自旋锁

Java有很多种锁:公平锁、非公平锁、可重入锁、递归锁、自旋锁、读锁、写锁、等等

公平和非公平锁

java.util.concurrent.locks.ReentrantLock可以通过指定构造函数的boolean类型来得到公平锁或者非公平锁,默认情况下将构造非公平锁。

是什么

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

//Creates an instance of ReentrantLock. This is equivalent to using ReentrantLock(false).
    public ReentrantLock() {
        sync = new NonfairSync();

构造方法有两个:
ReentrantLock()
ReentrantLock(boolean fair)
根据给定的公平政策创建一个 ReentrantLock的实例。(默认情况下创建的为非公平锁)

两者之间的区别

公平锁:并发环境下,每个线程获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的一个,就占有锁,否则就睡加入到等待队列中,以后会按照FIFO规则从队列中取到自己。
非公平锁:比较粗鲁,一开始就直接尝试占有锁,如果尝试失败,再采用类似公平锁的方式。

  • 非公平锁的优点在于吞吐量比公平锁大
  • 对于Synchronized而言,也是非公平锁。
    -------------------------------------------(分割线)--------------------------------------------------

可重入锁(又名递归锁)(ReentrantLock)

是什么

可重入锁,也叫递归锁。指的是

  • 同一线程外层函数获得锁之后,内层递归函数仍能获得该锁的代码。
  • 在同一个线程在外层方法获取锁的时候,再进入内层方法会自动获取锁。
    即: 线程可以进入任何一个他已经拥有的锁所同步着的代码块。
    //外层函数
    public synchronized void method01(){
        method02();//M2的锁由于M1 获得了锁而自然获得
    }
    //内层函数
    public synchronized void method02(){}

ReentrantLock/Synchronize都是典型的可重入锁(默认非公平)

可重入锁最大的作用就是避免死锁

死锁,M1获得了锁,调用M2,M2一直等待M1结束,M1会死锁
一系列流程使用同一个锁,执行方法1时候锁住,执行方法2时需要等待自己在方法1 中获得的锁解掉,形成了自己需要获得锁,自己又需要解掉锁的死锁情况。

可重入锁的种类

  • 隐式锁(即Synchronized关键字使用的锁)默认是可重入锁
  • 显式锁(即Lock)也有ReentrantLock这样的可重入锁

ReentrantLockDemo

class Phone implements Runnable
{
    public synchronized void sendSMS()throws Exception{
        System.out.println(Thread.currentThread().getName()+"\t-----------invoked sendSMS");
        sendEmail();
    }
    public synchronized void sendEmail()throws Exception{
        System.out.println(Thread.currentThread().getName()+"\t+++++++++++invoked sendEmail");
    }
    //----------------------------------------------------------------------
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        get();
    }
    private void get(){
        //加锁要和解锁个数配对!!(编译运行都不会报错,会有锁滞留)
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"\t-----------invoked get");
            set();
        }finally {
            //加锁要和解锁个数配对!!(编译运行都不会报错,会有锁滞留)
            lock.unlock();
        }
    }
    private void set() {
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"\t+++++++++++invoked set");
        }finally {
            lock.unlock();
        }
    }
}
public class ReentrantLockDemo {
    public static void main(String[] args) {
        System.out.println("-----------------------Synchronized");
        Phone phone = new Phone();
        new Thread(()->{
            try {
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"t1").start();
        new Thread(()->{
            try {
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"t2").start();

        try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("-----------------------ReentrantLock");
        new Thread(()->{
            phone.run();
        },"t3").start();
        new Thread(()->{
            phone.run();
        },"t4").start();
    }
}

Console:
-----------------------Synchronized
t1  -----------invoked sendSMS
t1  +++++++++++invoked sendEmail
t2  -----------invoked sendSMS
t2  +++++++++++invoked sendEmail
-----------------------ReentrantLock
t3  -----------invoked get
t3  +++++++++++invoked set
t4  -----------invoked get
t4  +++++++++++invoked set

加锁要和解锁个数配对!!(编译运行都不会报错,会有锁滞留)

自旋锁

是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式取尝试获取锁

  • 这样做的好处是减少线程上下文切换的消耗
  • 缺点是循环会消耗CPU。
  • 循环比较获取,直到成功为止,没有类似wait的阻塞。
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        //预期值和主物理内存中的值不一致就一直重新取(可能会出现多次循环)
        return var5;
    }

手写一个自旋锁

package com.company;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class SpinLockDemo {
    //原子引用线程
    AtomicReference atomicReference = new AtomicReference<>();
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"  came in for the Lock");
        while (!atomicReference.compareAndSet(null,thread)){
        //空循环-> 直到atomicReference为null时,执行CAS,并跳出循环
        }
    }
    public void unLock(){
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(thread.getName()+"  release the Lock");
    }
    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();
        new Thread(()->{
            spinLockDemo.myLock();
            System.out.println("Thread A get the Lock");
            try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}
            spinLockDemo.unLock();
        },"A").start();
        try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
        new Thread(()->{
            spinLockDemo.myLock();
            System.out.println("Thread B get the Lock");
            spinLockDemo.unLock();
        },"B").start();
    }
}

----------------------------------------------------------------------------------------------------
Console:
                     A  came in for the Lock
                     Thread A get the Lock
                     B  came in for the Lock
                             (5S后)
                     A  release the Lock
                     Thread B get the Lock
                     B  release the Lock

你可能感兴趣的:(【Java锁】(公平锁、非公平锁、可重入锁、递归锁、自旋锁)谈谈你的理解?手写一个自旋锁)