公平锁与非公平锁、可重入锁(递归锁)、自旋锁、独占锁(写锁)、共享锁(读锁)、互斥锁

前言

对java众多锁机制的学习,予以记录!

学习三板斧:理论、代码、小总结
缓冲的三大基本操作:读、写,清空

一、公平锁与非公平锁

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

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

1、优先级反转

一个线程执行完毕,本该为优先级高的2号线程执行,但由于非公平锁,使得8号线程获取到锁

2、饥饿现象

优先级低的反而频繁获取锁,造成其他线程因为没有获取到锁,而产生“饥饿”现象

3、两者区别

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

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

Lock lock = new ReentrantLock(true);

ReentrantLock可指定构造函数Boolean参数确定它为公平true or 非公平false,默认非公平锁。非公平锁吞吐量比公平锁大

Synchronized为非公平锁

二、可重入锁(递归锁)

1、理论

可重入锁(又名递归锁): 指在同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码, 在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。即: 线程可以进入任何一个它已经拥有的锁所同步着的代码块

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

2、代码验证:ReentrantLock,Synchronized是可重入锁

/*目的:验证synchronized与ReentrantLock为可重入锁*/
//声明资源类Phone,
class Phone implements Runnable{
	@Override
    public void run() {
        get();
    }
    // sendMess()与receiveMess()验证synchronized,其中sendMess()调用了receiveMess()
    public synchronized void sendMess(){
        System.out.println(Thread.currentThread().getName()+"\tsend message!");
        receiveMess();
    }
    public synchronized void receiveMess() {
        System.out.println(Thread.currentThread().getName()+"\treceive message!");
    }
    
    // get()与set()验证ReentrantLock,其中get()调用了set()
    ReentrantLock lock = new ReentrantLock();
    public void get(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"\tget!");
            set();
        }
        finally {
            lock.unlock();
        }
    }
    public void set(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"\tset!");
        }
        finally {
            lock.unlock();
        }
    }
}
public class ReentrantLockDemo{
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        //建立两个线程调用sendMess()
        for (int i = 0; i < 2; i++) {
            new Thread(()->{
                phone.sendMess();
            },"t"+String.valueOf(i)).start();
        }
        //建立两个线程调用get()
        TimeUnit.SECONDS.sleep(2);
        System.out.println();
        System.out.println();
        System.out.println();

        Phone p1 = new Phone();
        Phone p2 = new Phone();
        Thread thread1 = new Thread(p1,"t3");
        Thread thread2 = new Thread(p2,"t4");

        thread1.start();
        thread2.start();
    }
}

结果:

在这里插入图片描述

多执行几次:

在这里插入图片描述
在这里插入图片描述

3、结论

观察结果发现,在synchronized修饰的sendMess()中调用synchronized修饰的receiveMess()竟然是同一个线程,也就说明了synchronized是一个可重入锁;

在get()中调用set()中使用ReentrantLock加锁后竟然是同一个线程调用,也就说明了ReentrantLock也是一个可重入锁。

4、那么Synchronized与ReentrantLock有什么区别?

项目synchronizedlock
原始结构jvm层面关键字API层面java.util.concurrent.locks.Lock
使用方法不需要用户手动释放锁,底层为monitorenter与monitorexit(1:2)需要用户手动释放锁,lock()与unlock()在try catch finally中使用
等待是否可中断不可中断,除非抛出异常或者正常运行完成可中断,设置超时方法tryLock(Long timeout,TimeUnit unit);lockInterruptibly(),调用interrupt()可中断
加锁是否公平非公平锁默认为非公平锁可传入true指定为公平锁
有无绑定多个条件的condition没有,要么随即唤醒一个线程notify(),要么唤醒全部线程notifyall()有,可以精确唤醒需要唤醒的线程condition.await(),condition.singall()

5、问题:关于ReentrantLock的加锁与释放锁次数不一致

①get()中,次数:lock()=unlock(),结果:程序正常执行完成

在这里插入图片描述
结果:程序正常执行

在这里插入图片描述

②get()中,次数:lock()大于unlock(),结果:程序通过编译且可以运行,无法结束:由于缺少一次unlock()导致其他线程阻塞

在这里插入图片描述

结果:程序通过编译且可以运行,无法结束:由于缺少一次unlock()导致其他线程无法进行

在这里插入图片描述

③get()中,次数:lock() 小于unlock(),结果:程序运行完成但抛出异常illegalMonitorStateException

在这里插入图片描述

结果:程序运行完成但抛出异常illegalMonitorStateException

在这里插入图片描述

6、结论:lock次数需与unlock次数一致

1.次数unlock()==lock(),程序正常执行

2.次数unlock()<lock(),程序可以通过编译,也可以正常运行,但无法结束。由于缺少unlock(),其他线程无法继续执行。

3.次数unlock()>lock(),程序可以通过编译,也可以正常运行,但抛出异常。

三、自旋锁

1、理论

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

目的: 减少线程上下文频繁的切换而引起的性能损耗,所以才自旋让当前线程一直占用资源。

好处: 循环比较获取直到成功为止,没有类似wait()的阻塞

缺点: 循环会消耗CPU资源

2、代码验证

①AtomicInteger实现原理CAS

atomicInteger.getAndIncrement();

public final int getAndIncrement() {
        return U.getAndAddInt(this, VALUE, 1);
 }

public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
 }

②自己编写一个自旋锁

/*目的:验证自旋锁的处理机制*/
public class SpinLockDemo {
    //实现一个有关于线程的原子类引用
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    //线程获取锁
    public void mylock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"获得锁");
        while(!atomicReference.compareAndSet(null,thread)){
            
        }
    }
    //线程释放锁
    public void myUnlock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"释放锁");
        atomicReference.compareAndSet(thread,null);
    }

    public static void main(String[] args) throws InterruptedException {
        SpinLockDemo spinLockDemo = new SpinLockDemo();
        new Thread(()->{
            spinLockDemo.mylock();
            try {
                TimeUnit.SECONDS.sleep(4);//占用锁4s
                spinLockDemo.myUnlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"AAA").start();
        TimeUnit.SECONDS.sleep(1);
        new  Thread(()->{
            try {
                spinLockDemo.mylock();
                TimeUnit.SECONDS.sleep(4);//占用锁4s
                spinLockDemo.myUnlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"BBB").start();
    }
}

在这里插入图片描述

3、结论:自旋锁会使线程通过循环比较方式获取锁直到成功为止

四、读写锁

1、理论

独占锁(写锁):该锁一次只能被一个线程所持有

共享锁(读锁):该锁可被多个线程所持有,

synchronized --> ReentrantLock–> ReentrantReadWriteLock

ReentrantReadWriteLock读共享锁,写独占锁

读-读可共存
读-写不可共存
写-写不可共存

2、代码验证:模拟一个分布式缓存进行多线程读写操作

分布式缓存模拟:map实现

缓存三大基本操作:读,写,清空

业务要求:写操作 原子+独占 ,整个过程必须是一个完整的统一体,不许被分割,被打断

初始代码

/*模拟一个分布式缓存读、写与清空操作*/
class MyCatch {
    // 缓存模拟
    private volatile Map<String, Object> hashmap = new HashMap<>();

    public void put(String key, Object value) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + "\t正在写入:" + key);// 主要注重key的写入
        TimeUnit.MILLISECONDS.sleep(300);// 模拟网络延迟拥堵等情况
        hashmap.put(key, value);
        System.out.println(Thread.currentThread().getName() + "\t写入完成");
    }

    public void get(String key) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + "\t正在读取:" + key);// 主要注重key的写入
        TimeUnit.MILLISECONDS.sleep(300);// 模拟网络延迟拥堵等情况
        Object result = hashmap.get(key);
        System.out.println(Thread.currentThread().getName() + "\t读取完成,读取值为:" + result);
    }

}

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCatch myCatch = new MyCatch();
        for (int i = 1; i <= 4; i++) {
            final int tempInt = i;
            new Thread(() -> {
                try {
                    myCatch.put(tempInt + "", Integer.valueOf(tempInt));
                    TimeUnit.MILLISECONDS.sleep(300);
                    myCatch.get(tempInt + "");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

在这里插入图片描述

问题:发现不符合写操作的原子+独占

我们可以用Lock,Synchronized与ReentrantReadWriteLock
但是,前两者对于高并发读写操作有很大的性能负荷,不推荐。

代码:

class MyCatch {
    // 缓存模拟
    private volatile Map<String, Object> hashmap = new HashMap<>();
    ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) throws InterruptedException {
        try {
            rwLock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + "\t正在写入:" + key);// 主要注重key的写入
            TimeUnit.MILLISECONDS.sleep(300);// 模拟网络延迟拥堵等情况
            hashmap.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t写入完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwLock.writeLock().unlock();
        }

    }

    public void get(String key) throws InterruptedException {
        try {
            rwLock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + "\t正在读取:" + key);// 主要注重key的写入
            TimeUnit.MILLISECONDS.sleep(300);// 模拟网络延迟拥堵等情况
            Object result = hashmap.get(key);
            System.out.println(Thread.currentThread().getName() + "\t读取完成,读取值为:" + result);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
        }
    }
}

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCatch myCatch = new MyCatch();
        for (int i = 1; i <= 4; i++) {
            final int tempInt = i;
            new Thread(() -> {
                try {
                    myCatch.put(tempInt + "", Integer.valueOf(tempInt));
                    TimeUnit.MILLISECONDS.sleep(300);
                    myCatch.get(tempInt + "");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

在这里插入图片描述

3、结论:读写锁ReentrantReadWriteLock可以保证缓存中的读共享,写独占操作

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值