可重入锁+自旋锁+读写锁

可重入锁

我们来看什么是可重入锁

可重入锁的核心意思 是同一个线程在外层方法获取锁的时候 在进入内层方法会自动获取锁

这里我们分别举synchronized 和 ReentrantLock的例子

synchronized是可重入锁

package com.robot;

class Phone{
    //外层方法
	public synchronized void sendMes() {
		System.out.println(Thread.currentThread().getName()+"\t sendMes");
		call();//进入内层方法会自动获取锁
	}
	//内层方法也别synchronized修饰
	public synchronized void call() {
		System.out.println(Thread.currentThread().getName()+"\t call");
	}
}
public class Demo1 {
	public static void main(String[] args) {
		Phone phone=new Phone();
		new Thread(()->{
			phone.sendMes();
		},"t1").start();
	
	}
}
//控制台结果
t1	 sendMes
t1	 call

ReentrantLock是可重入锁

package com.zyk;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Phone{
    //两个方法持有同一个lock
    Lock lock=new ReentrantLock();//默认是非公平锁
    //外层方法
    public void sendMes(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t sendMes");
            //外层方法调用内层方法 自动获取锁
            call();
        }finally {
            lock.unlock();
        }

    }
    //内层方法被lock锁锁住
    public void call(){
      lock.lock();
      try {
          System.out.println(Thread.currentThread().getName()+"\t call");
      }finally {
          lock.unlock();
      }
   }
}
public class ReentrantLockDemo {
    public static void main(String[] args) {
        Phone phone=new Phone();
        new Thread(()->{
            phone.sendMes();
        },"A").start();
    }
}
结果
A	 sendMes
A	 call

自旋锁

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

我们来模拟一种自旋的情况

package com.zyk;

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

public class SpinLockDemo {
    //原子引用 引用的是线程
    AtomicReference<Thread> atomicReference=new AtomicReference<>();

    public void  myLock(){
        Thread thread=Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"进来了");
        while(!atomicReference.compareAndSet(null,thread)){

        }
    }

    public void myUnlock(){
        Thread thread=Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"离开了");
    }
    public static void main(String[] args) {
        SpinLockDemo spinLockDemo=new SpinLockDemo();
        new Thread(()->{
            spinLockDemo.myLock();
            try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
            spinLockDemo.myUnlock();
        },"AAA").start();
        //确保AAA线程先拿到锁
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(()->{
            spinLockDemo.myLock();
            spinLockDemo.myUnlock();
        },"BBB").start();
    }
}

结果
AAA进来了
BBB进来了
AAA离开了
BBB离开了

这块我们具体分析一下这个上述的过程

AAA首先获得了锁 —》等待3秒之后 BBB进入这个myLock方法 在while循环这里 CAS比较不满足条件 (因为此时真实值不是null)就一直在这里执行自旋过程—》再过7秒之后 AAA释放锁—》BBB拿到锁 结束自旋–》

注意

BBB进来了之后 并没有拿到锁 在这7秒钟之内 会一直自旋尝试获取

如果代码是这样

 while(!atomicReference.compareAndSet(null,thread)){
            System.out.println(Thread.currentThread().getName()+"在这里自旋");
        }

就会发现 在BBB3秒钟这个时刻进来之后 到第10秒的这7秒内 控制台会打印满屏的在这里自旋

读写锁

独占锁 (读锁) 该锁一次只能被一个线程锁持有 ReentrantLock和synchronized都是独占锁

共享锁 (写锁) 该锁可以被多个线程锁持有

我们分析一个具体生活案例 在机场的显示屏上显示着各个航班的信息 那么 我们底下围着一圈人去看这个信息 这就好比共享锁 (肯定不能一个个人去排队看)这样就提高了我们的并发性 但是在某个航班延误的时候 只能有机场的工作人员去进行修改 这就好比写锁 如果这时候每个人每个线程都去写 呢这就直接乱了套了

我们先看一个没有加锁的错误示例

package com.zyk;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

class MyCache{
    private volatile Map<String,Object> map=new HashMap<>();

    public void put(String key,Object value){
        System.out.println(Thread.currentThread().getName()+"正在写入"+key);
        try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName()+"写完了");

    }
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"正在读取");
        try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        Object result=map.get(key);
        System.out.println(Thread.currentThread().getName()+"读完了"+result);

    }

}
public class ReadAndWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache=new MyCache();
        for (int i = 1; i <=5 ; i++) {
            final int temp=i;
            new Thread(()->{
                myCache.put(temp+"",temp+"");
            }).start();
        }

        for (int i = 1; i <=5 ; i++) {
            final int temp=i;
            new Thread(()->{
              myCache.get(temp+"");
            }).start();
        }
    }
}

在这里插入图片描述

这个时候我们如果使用了传统lock锁 呢么虽然数据安全性得到保证 但是并发的性能就会大大的下降 这是juc为我们提供了一种解决方式ReentrantReadWriteLock

我们这里在写的时候将会使用到 reentrantReadWriteLock.writeLock().lock();这个方法

我么这里在读的时候将会使用到reentrantReadWriteLock.readLock().lock();这个方法

package com.zyk;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache {
    //写入类似缓存的东西用volatile修饰 保证可见性 第一时间拿到数据
    private volatile Map<String, Object> map = new HashMap<>();

    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
        reentrantReadWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始写" +key);
            try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写完了");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }

    public void get(String key) {
        reentrantReadWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始读");
            try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读结束" + result);

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


public class ReadAndWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache=new MyCache();
        for (int i = 1; i <=5 ; i++) {
            final int temp=i;
            new Thread(()->{
                myCache.put(temp+"",temp+"");
            }).start();
        }

        for (int i = 1; i <=5 ; i++) {
            final int temp=i;
            new Thread(()->{
              myCache.get(temp+"");
            }).start();
        }
    }
}

在这里插入图片描述

在这里插入图片描述

这里的结果就既保证了数据一致性 又保证了并发性 达到了我们满意的效果

nice 收工了 !!

晚安 好梦 希望大家心想事成


发布了39 篇原创文章 · 获赞 19 · 访问量 1477

猜你喜欢

转载自blog.csdn.net/weixin_44222272/article/details/105306645