java 锁

公平锁和非公平锁

什么是公平锁

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

什么是非公平锁

多个线程获取锁的顺序并不是按照锁的顺序,有可能后申请的线程比现申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或优先级饥饿现象

优先级反转:A 线程先申请获取锁,B后申请,但是B优先获得锁,然后是A

优先级饥饿:A、B、C 3个线程按照顺序申请获取锁,到快要执行B的时候,C加塞 获取B的锁资源,每次B要获取的时候,C都来加塞,导致B卡住一天了都没有执行

创建

juc包中,ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是非公平锁

在这里插入图片描述

synchronized也是非公平锁

公平锁和非公平锁的区别是什么

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

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

非公平锁的优点:吞吐量大

可重入锁(又名递归锁)

什么是可重入锁

同一个线程在外层方法 A 获取锁的时候,在进入内层方法 B 会自动获取锁 ( A 和 B 都使用了sync关键词,或使用了ReentryLock )

---- 线程可以进入任何一个他已经拥有的锁 所同步着的代码块

可重入锁synchronized 示例

在Phone中的 call方法调用了另一个 sync 方法 sendMsg, 这个过程看下来 就相当于是 sendMsg方法 对于 call方法来说 sendMsg的所有代码都是写在 call() 方法中的一样,他俩共用一个线程执行

public class ReentryLockTest {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() ->{
            phone.call();
        }, "aa").start();

        new Thread(() ->{
            phone.call();
        }, "bb").start();
    }
}

class Phone{
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName() + ", 打电话");
        this.sendMsg();
    }

    public synchronized void sendMsg(){
        System.out.println(Thread.currentThread().getName() + ", 发短信");
    }
}

执行结果如下
在这里插入图片描述

可重入锁 ReentryLock案例
public class ReentryLockTest {
    public static void main(String[] args) {
        Phone1 p1 = new Phone1();
        Thread thread1 = new Thread(p1,"aa");
        thread1.start();
        Thread thread2 = new Thread(p1, "bb");
        thread2.start();
    }

}

class Phone1 implements Runnable{

    private ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        this.call();
    }

    public void call(){
        reentrantLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ", 调用call方法");
            this.sendMse();
        }finally {
            reentrantLock.unlock();
        }
    }

    public void sendMse(){
        reentrantLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ", 调用sendMsg方法");
        }finally {
            reentrantLock.unlock();
        }
    }
}

执行结果如下
在这里插入图片描述

可重入的锁的优点

避免了死锁:为什么说可重入锁避免了死锁:那sync举个例子:首先进入call方法,方法上锁,如果不是可重入锁的话,那么就不会进入 sendMsg方法,因为在外层已经上锁了,就不能在进入另一个方法了,就只能眼巴巴的等着,知道外层的这个锁解锁了之后,才能进入另一个方法

自旋锁

什么是自旋锁

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

/**
 * 自旋锁 代码验证
 * 首先什么是自旋锁:尝试获取锁的线程不会立即阻塞,而是采用循环获取的方式尝试获取锁
 * 思路:
 *  A,B 2个线程一起操作一个共享资源,A线程1直占用着这个共享资源,B线程采用循环的方式尝试获取这个共享资源
 *  A 释放锁,此时 B 修改这个共享资源,然后打印输出结果
 */
public class SpinLockTest {

    static AtomicReference<String> atomicReference = new AtomicReference<>();

    public static void main(String[] args) {

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
                //sout中的内容最好不要分2行,可能还没打印出第2行, 共享变量就已经被 bb 修改完了
                System.out.println(atomicReference.compareAndSet(null, "over") + "现在的结果是"+ atomicReference.get());
                System.out.println(Thread.currentThread().getName()+ ", aa 线程忙完了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "aa").start();


        new Thread(()->{
            try {
                //停顿1s 的目的是为了确保上面那个线程先启动
                TimeUnit.SECONDS.sleep(1);
                System.out.println("bb线程开始执行");
                while (!atomicReference.compareAndSet("over", null)){
                    System.out.println("bb 线程等待 aa 线程忙完");
                }
                System.out.println("bb线程修改完共享变量的值,现在的值为" + atomicReference.get());
            }catch (Exception e){
                e.printStackTrace();
            }
        }, "bb").start();
    }
}
自旋锁的优缺点

优点:其他线程不用进入阻塞状态,

缺点:如果循环次数过多 消耗CPU 导致系统性能下降

独占锁(写锁) / 共享锁(读锁)

创建方式

独占锁,共享锁就是通常说的读写锁, 使用ReadWriteLock 接口种的 ReentrantReadWriteLock 创建

什么是 ReadWriteLock,ReadWriteLock 和 Lock的区别是什么,为什么要使用 ReadWriteLock 而不是 Lock

ReadWriteLock和Lock的区别

ReadWriteLock:写操作的时候独占锁,读操作共享 可以不加锁,读读可共享,写读写写要独占锁

Lock:写的时候只有1个人可以写,读的时候也只有1个人可以读( 读的时候1个人可以读不科学,读操作不会出现并发问题 应该允许多人读取,只有读和写同时存在的时候才会出现并发问题 )

案例1:

public class ReadWriteLockTest {

    public static void main(String[] args) {

        LockCache lockCache = new LockCache();

        //创建5个线程, 5个线程读, 5个线程写
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            new Thread(() -> {
                lockCache.write(String.valueOf(finalI), finalI);
            }, "aa").start();
        }

        for (int i = 0; i < 5; i++) {
            int finalI = i;
            new Thread(() -> lockCache.read(String.valueOf(finalI)), "bb").start();
        }
    }
}

class LockCache {

    /**
     * 创建一个读写锁
     */
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    /**
     * 共享资源
     */
    private Map<String, Object> cache = Maps.newHashMap();

    public void write(String key, Object obj) {
        reentrantReadWriteLock.writeLock().lock();
        System.out.println("开始写入 key: " + key + ", value: " + obj);
        cache.put(key, obj);
        System.out.println("写入完成");
        reentrantReadWriteLock.writeLock().unlock();
    }

    public void read(String key) {
        reentrantReadWriteLock.readLock().lock();
        System.out.println("开始读取");
        cache.get(key);
        System.out.println("读取完成, value = " + cache.get(key));
        reentrantReadWriteLock.readLock().unlock();
    }
}

案例2

class ReadWriteCache {
    private volatile Map map = Maps.newHashMap();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public void read(Integer key) {
        readWriteLock.readLock().lock();
        System.out.println("开始读取");
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.get(key);
        System.out.println("读取成功,值为:" + map.get(key));
        readWriteLock.readLock().unlock();
    }
    public void write(Integer key, Integer value) {
        readWriteLock.writeLock().lock();
        System.out.println("开始写入:" + key);
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key, value);
        System.out.println("写入成功:");
        readWriteLock.writeLock().unlock();
    }
}


public class ReadWriteLockDemo {
    public static void main(String[] args) {
        ReadWriteCache readWriteCache = new ReadWriteCache();
        for (int i = 1; i <= 5; i++) {
            int key = i;
            int value = i;
            new Thread(() -> {readWriteCache.write(key, value);}, String.valueOf(i)).start();
        }


        for (int i = 1; i <= 5; i++) {
            int key = i;
            new Thread(() ->readWriteCache.read(key),String.valueOf(i)).start();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值