公平锁、非公平锁、可重入锁、自旋锁、读写锁

公平锁与非公平锁

  • 公平锁
    指多个线程按照申请锁的顺序来获取锁,按照先来先服务的原则。其落地实现可使用队列。

  • 非公平锁
    多个线程获取锁的方式不是按照申请锁的顺序,后申请锁的线程可以比先申请锁的线程先获得锁。

  • JUC下的ReentrantLock的构造boolean值参数为锁的种类,true为公平锁,false为非公平锁;
    代码如下:

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

公平锁和非公平锁的区别

1)公平锁在并发环境中获取锁时会查看锁维护的等待队列,如果队列为空,或者当前线程是等待队列的第一个,就占有锁,否则加入等待队列,按照FIFO等待获取锁。
2)非公平锁直接尝试占有锁,如果尝试失败,就在采用类似公平锁的方式获取锁。

注意:synchronized是一种非公平锁

synchronized与ReentrantLock的区别

可重入锁

  • 已获取锁的线程可以进入该锁锁住的同步代码;

  • 同一线程外层函数获得锁之后,内层递归函数仍然能获取锁的代码。同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

  • 可重入锁最大作用就是规避死锁;

代码示例

/*
 * 可重入锁(也就是递归锁)
 *
 * 指的是同一个线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,
 * 在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
 *
 * 也就是说,线程可以进入任何一个它已经拥有的锁所有同步着的代码块。
 *
 * t1   invoked sendSMS()      t1线程在外层方法获取锁的时候
 * t1   invoked sendEmail()    t1在进入内层方法会自动获取锁
 * t2   invoked sendSMS()
 * t2   invoked sendEmail()
 *
 */
public class RenenterLockDemo {
    public static void main(String[] args) {
        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();
    }

}

class Phone {
    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()");
    }
}

可以证明synchronized是一种可重入锁;进入sendSMS方法后,还能再访问另外一个同步方法sendEmail;
也证明了sychronized锁的当前实例对象;

package com.juc;

import java.util.concurrent.locks.ReentrantLock;

public class ReentryLock {
    public static void main(String[] args) {
        Phone phone=new Phone();
        new Thread(()->{
            phone.set();
        },"AAA").start();
        new Thread(()->{
            phone.set();
        },"BBB").start();
    }
}
class Phone{
    public ReentrantLock lock=new ReentrantLock();
    public void set(){
        lock.lock();
        try{
            System.out.println("set.....");
            get();
        }catch(Exception e){

        } finally{
            lock.unlock();
        }
    }
    public void get(){
        lock.lock();
        try{
            System.out.println("get....");
        }catch(Exception e){

        } finally{
            lock.unlock();
        }
    }
}

可以证明ReentrantLock 是一种可重入锁;进入set的同步代码后,还能再访问另外一个get方法里的代码块;

自旋锁

尝试获取锁的线程不会立刻阻塞,而是通过循环的方式等待获取锁。而不用再操作系统层面被挂起(用户态-》内核态),避免了上下文切换。
场景:线程执行任务的时间较短,可以较快的释放锁,在循环等待期间就可以获取到锁了。

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;
}

这里涉及到了CAS;

自旋锁代码示例

使用原子引用封装Thread,通过CAS算法实现线程的自旋锁

/**
 * 写一个自旋锁
 * 自旋锁的好处:循环比较获取直到成功为止,没有类似wait的阻塞。
 *
 * 通过CAS操作完成自旋锁:
 *  A线程先进来调用myLock方法自已持有锁5秒钟
 *  B随后进来后发现当前有线程持有锁,不是null,
 *  所以只能通过自旋等待,直至A释放锁后B随后抢到
 *
 * @ClassName SpinLockDemo
 * @Description TODO
 * @Author Heygo
 * @Date 2020/8/8 13:37
 * @Version 1.0
 */
public class SpinLockDemo {
    // 泛型为 Thread
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        // 获取当前线程
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t come in ");
        /*
         自旋:
            期望值为 null 表示当前没有线程
            新值为 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() + "\t invoked myUnLock()");
    }

    public static void main(String[] args) {
        // 原子引用线程
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
            spinLockDemo.myLock(); // 加锁
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnLock(); // 解锁
        }, "AA").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            spinLockDemo.myLock(); // 加锁
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnLock(); // 解锁
        }, "BB").start();
    }
}
程序运行结果:核心为 CAS 算法
线程 A 先执行,此时期望值为 null ,线程 A 将获得锁,并将期望值设置为线程 A 自身
线程 B 尝试获取锁,发现期望值并不是 null ,就在那儿原地自旋
线程 A 释放锁之后,将期望值设置为 null ,此时线程 B 获得锁,将期望值设置为线程 B 自身
最后线程 B 释放锁3


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

  • 独占锁:指的是该锁一次只能被一个线程持有,对于ReentrantLock和synchronized都是独占锁。
  • 共享锁:该锁可以被多个线程共同持有。
  • ReentrantWriteReadLock其内部的读锁是共享锁,写锁是独占锁;
public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCache myCache=new MyCache();
        for (int i = 0; i < 5; i++) {
            final int index=i;
            new Thread(()->{
                myCache.put(String.valueOf(index),index);
            },String.valueOf(i)).start();
        }
        for (int i = 0; i < 5; i++) {
            final int index=i;
            new Thread(()->{
                myCache.get(String.valueOf(index));
            },String.valueOf(i)).start();
        }

    }
}
class MyCache{
    public volatile Map<String,Object> map=new HashMap<>();
    ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    public void put(String key,Object value){
        lock.writeLock().lock();
        try{
            System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
            Thread.sleep(3000);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "\t 写入完成");
        }catch(Exception e){

        } finally{
            lock.writeLock().unlock();
        }
    }
    public void get(String key){

        lock.readLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"读取---");
            Object val=map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取结束result="+val);
        }catch(Exception e){

        } finally{
            lock.readLock().unlock();
        }
    }
}


程序运行结果:写操作没有被打断,读操作可以同时进行


1	 正在写入:1
1	 写入完成
2	 正在写入:2
2	 写入完成
4	 正在写入:4
4	 写入完成
3	 正在写入:3
3	 写入完成
0读取---
0读取结束result=0
1读取---
1读取结束result=1
2读取---
3读取---
3读取结束result=3
4读取---
2读取结束result=2
4读取结束result=4

ReentrantReadWriteLock源码

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;

分别维护了一个读锁,写锁;

另外Synchronized在锁升级的过程会涉及到一些锁、偏向锁、轻量级锁、重量级锁。
可以查看这篇博文
10分钟必懂-深入理解Synchronized关键字

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值