并发学习--锁

本文深入探讨了Java中的Lock接口及其具体实现ReentrantLock,对比了synchronized的不足。重点讲解了ReentrantLock的tryLock、lockInterruptibly和公平/非公平策略。通过实例展示了如何避免死锁、响应中断以及锁的可重入性。此外,还介绍了读写锁的概念和自旋锁的原理,分析了锁的优化策略,如自旋锁的自适应和锁消除。
摘要由CSDN通过智能技术生成

在这里插入图片描述
章节情况
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第一部分
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
为什么需要lock
在这里插入图片描述
第一点:就是说synchronized 它只有在执行完代码,或者里面抛出异常的时候,才会进行释放锁。
第二点:不够灵活,加锁和释放锁的时机单一
第三点:不知道能不能获取到锁
lock的优点
在这里插入图片描述

lock():方法要是陷入了死锁,就会一直进行等待。所以,也非常的不好。
为了解决这个问题,出现了tryLock()这个方法
在这里插入图片描述
下面先介绍tryLock的兄弟方法
在这里插入图片描述
如果一定时间内,可以得到锁就成功,不能得到就放弃。用了tryLock这个方法可以避免死锁的问题,一般来说,死锁就是,锁一获取到了A对象,而这个A对象想去获取B对象内容。而锁二获取到了B对象,而它想去获取A对象的内容,由于它们相互锁着,着就导致了死锁的产生。这时候,如果使用了tryLock就可以解决这个问题了。就以刚刚这个例子为例:锁获取了A对象,而这个A对象想去获取B对象,而B对象呢也被锁住,它想去获取A对象。这个时候,如果用tryLock去获取的话,就可以给他传入时间,如果一定时间内获取不到,那么就代表获取失败,这时候我可以先将我锁住的A对象进行释放。然后,再重新执行之前的程序,那么由于A对象被释放了,所以,锁二就可以获取到A对象了。最终结果就是B对象也会被释放,当之前程序第二次执行时,就可以先锁A对象,再锁B对象了。
tryLock:成功避免了死锁的方法,虽然他们会进行争夺资源,但是他们也会进行谦让。成功避免了死锁发生。
上述代码如下:

package lock;

import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 描述: 用tryLock来避免死锁
 */
public class TryLockDeadlock implements Runnable {
    //采取不同的标志位来执行不同的逻辑,来模拟锁争抢的情况
    int flag = 1;

    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        TryLockDeadlock r1 = new TryLockDeadlock();
        TryLockDeadlock r2 = new TryLockDeadlock();
        r1.flag = 1;
        r2.flag = 0;
        new Thread(r1).start();
        new Thread(r2).start();

    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (flag == 1) {
                try {
                    if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
                        System.out.println("线程1获取到了锁1");
                        Thread.sleep(100);

                        //在里面已经获取到第一把锁了,现在准备尝试获取第二把锁
                        if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
                            try {
                                System.out.println("线程1获取到了锁2");
                                System.out.println("线程1获取到了两把锁");
                            } finally {
                                lock2.unlock();
                            }
                        } else {
                            System.out.println("线程1没有获取到第二把锁,已重试");
                        }
                        //说明没有拿到这把锁
                    } else {
                        //它获取失败,但是,还是想重新获取,就使用for来实现
                        System.out.println("线程1获取锁1失败,已重试");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock1.unlock();
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        if (flag == 0) {
            try {
                if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
                    System.out.println("线程2获取到了锁2");
                    Thread.sleep(100);

                    //在里面已经获取到第一把锁了,现在准备尝试获取第二把锁
                    if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {

                        try {
                            System.out.println("线程2获取到了锁1");
                            System.out.println("线程2获取到了两把锁");
                        } finally {
                            lock1.unlock();
                        }
                    } else {
                        System.out.println("线程2没有获取到第二把锁,已重试");
                    }

                    //说明没有拿到这把锁
                } else {
                    //它获取失败,但是,还是想重新获取,就使用for来实现
                    System.out.println("线程2获取锁2失败,已重试");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock2.unlock();
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


在这里插入图片描述
在等待锁的过程中,线程可以被中断,这个是synchronized所不具备的能力。
测试代码如下:

package lock;

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

public class LockInterruptibly implements Runnable {
    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        LockInterruptibly lockInterruptibly = new LockInterruptibly();
        Thread thread0 = new Thread(lockInterruptibly);
        Thread thread1 = new Thread(lockInterruptibly);
        thread0.start();
        thread1.start();
        try{
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        thread0.interrupt();
    }

    @Override
    public void run() {
        System.out.println( Thread.currentThread().getName() +  "尝试获取锁");
        try {
            //如果拿不到锁就会在等待
            lock.lockInterruptibly();
            try {
                System.out.println(Thread.currentThread().getName() +  "获取到了锁");
                //不然第二个线程轻易的拿到锁
                Thread.sleep(5000);
            }catch (InterruptedException e){
                System.out.println("睡眠期间被中断了");
            }finally {
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放了锁");
            }
        } catch (InterruptedException e) {
            System.out.println("等锁期间被中断了");

        }
    }
}

结果如下:
在这里插入图片描述
新建了两个线程,其中肯定有一个线程会先获取到锁的,但是不知道是哪一个能最先获取到。如上面我的测试,是Thread 0 先获取到锁,然后,Thread 0 就会一直执行,这时候,它在sleep过程中,被打断了。这时候,0就会释放锁。Thread 1就可以获得锁,然后1就会执行,然后执行结束,进行释放。
那么如果就这个情况,打断的是Thread1 由于它是在等待期间被中断的 。那就不会再执行了。结果如下:
在这里插入图片描述

这个中断功能是synchronized所不具备的功能。所以,lock比较灵活。
在这里插入图片描述
unlock要注意的就是获取锁后 先写一个try 然后,再写一个finally 把它进行解锁。确保锁安全。因为,要是忘了unlock 或者中途中抛出异常而没有执行unlock,导致没有解锁的话,就会陷入死锁。
在这里插入图片描述
在tryLock期间和lockInterruptibly期间都可以响应中断。
在这里插入图片描述
锁的分类:
在这里插入图片描述

在这里插入图片描述
悲观锁:互斥同步锁
乐观锁:非互斥同步锁
在这里插入图片描述
优先级反转:解释如下:如果线程中有一个优先级比较低,但是,它获取到了执行的权利,在他执行过程中,锁住了对象,如果锁的时间过长,其他线程虽然,优先级比较高,但是,它还是需要进行等待。这种就叫做优先级反转。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
具体的逻辑如下:线程一和线程二对共享资源同时进行计算,如果线程一计算完毕,发现他修改期间的数据没有被别改过。那么他就进行修改。而线程二进行计算,这个时候,他发现,他修改的数据已经被该过了,那么他就选择报错或者重试了。
在这里插入图片描述
在这里插入图片描述
因为如果使用悲观锁,我在写代码的时间内 我会把仓库锁住,你是不能提交的。如果我写了一天 那么这一天你就不能提交了。
在这里插入图片描述
在这里插入图片描述
总结一下:悲观锁适用于写入多,并发大,乐观锁适用于,写入少,大部分是读取的场景。让读取性能极大提高。
在这里插入图片描述
场景:电影院买票

package lock.reentrantlock;

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

/**
 * 通过多线程模拟电影院预定座位
 * 证明了ReentrantLock是一个可重用锁
 */
public class CinemaBookSeat {
    private static Lock lock = new ReentrantLock();
    public static void bookSeat(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "开始预定座位");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "完成预定座位");
            Thread.sleep(500);
        }catch(Exception e){
            e.printStackTrace();
        }
            finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(() -> {
            bookSeat();
        }).start();
        new Thread(() -> {
            bookSeat();
        }).start();
        new Thread(() -> {
            bookSeat();
        }).start();
        new Thread(() -> {
            bookSeat();
        }).start();
    }
}

结论如下:
在这里插入图片描述
一定要等第一个线程先释放了锁,剩下的线程才可以继续获得该锁。也是符合他的使用场景的。
总结:当我们需要确保任务的顺序时,就可以使用锁来提供帮助。
reentrant:可再进入的,可重入的。
在这里插入图片描述

可重入锁:同一个线程可以多次获取到同一个锁。
好处:避免死锁
假设两个方法都被锁锁住了,A运行第一个方法,它拿到这个锁了,如果它不是可重入锁,它他就没办法运行B方法了,因为要无法拿到B这个锁,需要A先释放锁,B再去拿这个锁。如果不是可重入性就死锁了,手里拿着这把锁,还想获得这把锁,这样就是死锁。

package lock.reentrantlock;

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

public class Test {
    private static Lock lock = new ReentrantLock();
    private void a(){
        lock.lock();
        try{
            System.out.println("执行a方法");
            b();
        }finally {
            lock.unlock();
        }
    }
    private void b(){
        lock.lock();
        try{
            System.out.println("执行b方法");
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        //由于ReentrantLock是可重入性锁,所以,a拿到了该锁,b也可以拿到该锁。两个方法都可以拿到该锁,不会导致死锁
        new Test().a();
    }
}

结论如下:
在这里插入图片描述

package lock.reentrantlock;

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

public class Test {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        /**
         *   注意在Lock接口中是没有这个方法的,只有在ReentrantLock这个类中有
         *   通过这个方法来查询当前线程锁定的个数
         */
        System.out.println(lock.getHoldCount());
        lock.lock();
        System.out.println(lock.getHoldCount());
        lock.lock();
        System.out.println(lock.getHoldCount());
        lock.lock();
        System.out.println(lock.getHoldCount());
        lock.lock();
        System.out.println(lock.getHoldCount());
        lock.unlock();
        System.out.println(lock.getHoldCount());
        lock.unlock();
        System.out.println(lock.getHoldCount());
        lock.unlock();
        System.out.println(lock.getHoldCount());
        lock.unlock();
        System.out.println(lock.getHoldCount());
    }
}

通过getHoldCount()这个方法可以查询当前线程保持此锁定的个数,即调用lock()方法的次数。
总结:可重入性,就是当前线程可以调用它锁多次,而且,不同方法之间也可以进行调用锁来进行锁定。并不会说在同一个线程中,这个锁锁住了就不能获取它了。
可重入锁分析:
在这里插入图片描述
c代表的是当前的占有锁的线程个数,如果c > 0 说明当前的线程已经有锁锁住了, 则对它进行加一。
如果释放锁的话也是如此。当c==0时,才给他进行释放。
在这里插入图片描述
非可重入锁,它只有 0 和 1这两种情况。
可重入锁一些其他方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
举个例子:比如说买火车票,我前面的人买完了,然后,轮到我了,这时候,我由于排太久队伍还蒙了一下。这时候,他回来问了一句说,几点开车。他是属于清醒状态。而我是属于蒙的状态。这时候他确实也算是插队了。java默认策略是非公平的
在这里插入图片描述
因为,在把挂起的线程恢复的时间,它是需要时间的。如果严格要求公平,那么这段时间谁都拿不到锁。 但是,如果采用非公平的策略。那么可能发生如下的情况:
A请求锁,B线程被挂起,当A释放锁的时候,这个时候,B进行唤醒。但是,此时,C希望拿到锁,那么C就去拿锁了。这个时候,C进行执行完毕。进行释放,然后B也唤醒完全,这时候B也可以拿到这个锁。这就实现了双赢。
ReentrantLock默认是一个非公平锁,但是,在创建的时候,传入true就会成为公平锁了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面以代码来演示

package lock.reentrantlock;


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

/**
 * 演示公平和不公平两种情况
 */
public class FairLock {
    public static void main(String[] args) {
        PrintQueue printQueue = new PrintQueue();
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(new Job(printQueue));
        }
        for (int i = 0; i < 10 ; i++) {
            threads[i].start();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
class  Job implements Runnable{
    private  PrintQueue printQueue;
    public Job(PrintQueue printQueue) {
        this.printQueue = printQueue;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始打印");
        printQueue.printJob(new Object());
        System.out.println(Thread.currentThread().getName() + "打印完毕");
    }
}
class PrintQueue{
    //创建一个公平锁
    private Lock queueLock = new ReentrantLock(true);
    public void printJob(Object document){
        queueLock.lock();
        try{
            System.out.println(Thread.currentThread() + "正在打印,需要1秒");
            Thread.sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            queueLock.unlock();
        }
        //如果使用的是非公平锁,这时候,虽然线程1-9都在排队,但是此时线程0又需要锁了,所以,会先给线程0
        queueLock.lock();
        try{
            System.out.println(Thread.currentThread() + "正在打印,需要1秒");
            Thread.sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            queueLock.unlock();
        }
    }
}

上面使用的是公平锁,那么他的执行顺序完全就是按照排队的队列中来。
在这里插入图片描述
这里是按照顺序添加的十个线程:每个线程都有打印任务,都需要获取锁,那么结果就是
在这里插入图片描述
在这里插入图片描述
当线程9执行完以后,按照排队规则,下一个应该就是线程0 会严格按照顺序来进行执行。
当使用的是非公平策略时。
在这里插入图片描述
当线程0执行完打印以后,释放锁后,队列中拍着线程1正准备唤醒,这时候,线程0又想获得锁了。这时候,因为线程0是处于活动状态,所以,系统会把这个锁先给线程0,这样就是非公平策略。
结果如下:符合预期
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
公平锁会去判断,有没有在队列中,但是非公平锁就不会去判断。它是直接尝试去获取

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

在这里插入图片描述

这就是解决了ReentrantLock的问题。就是在读的过程中是没有线程安全问题的
在这里插入图片描述
因为要是有人读有人写的话,会出现问题。
在这里插入图片描述
案例:电影院升级版
在这里插入图片描述
在这里插入图片描述
程序模拟:

package lock.reentrantlock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CinemaReadWrite {
    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    //生成一个读锁
    private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    //生成一个写锁
    private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    private static void read(){
        readLock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            System.out.println(Thread.currentThread().getName() + "释放了读锁");
            readLock.unlock();
        }
    }

    private static void write(){
        writeLock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写数据");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            System.out.println(Thread.currentThread().getName() + "释放了写锁");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(() -> read(),"Thread1").start();
        new Thread(() -> read(),"Thread2").start();
        new Thread(() -> write(),"Thread3").start();
        new Thread(() -> write(),"Thread4").start();
    }
}

结论如下:
在这里插入图片描述
线程1 和线程2是可以一起读的,但是,线程3想去写的时候,发现前面有线程在读,那不行啊。就在等待,等待他执行完毕以后,在进行写,当线程4想去写的时候,发现也不行,线程3还在写,于是,就等线程3执行完毕以后,才执行线程4。
在这里插入图片描述
在这里插入图片描述
如果读的线程太多 ,可能线程3很久都不能写 ,因为会有很多线程再来想读取。
在这里插入图片描述
在这里插入图片描述
总结:
在这里插入图片描述
注意:这里的写锁插队指的是,如果读锁在读,想获取写锁,就可以插队。防止它一直写不到
如果是,写锁在写,那么它也得乖乖的等着,直到轮到它为止,当然,前面如果是读锁,也可能被其他读锁在进行插队。
首先,先进入ReentrantReadWriteLock源码,在里面的结构中找到,NonfairSync和FairSync其对应的是公平和非公平在这里插入图片描述
在这里插入图片描述
在这里看读锁是不是应该排队和写锁是不是应该排队。
在这里插入图片描述
所以,在公平的情况下,只要队列中有人在排队,你就得排队。
现在,我们看一下非公平的情况:
在这里插入图片描述
它是直接返回false 直接就是不排队的,等前面的读执行完毕,就开始写(就是开始插队),因为一般读锁的情况多。
所以,对于非公平策略,写锁直接就可以插队,当然,如果是前面有读锁的话,要等他执行完毕。
读锁 调用的FirstQueuedIsExclusive()判断队列的第一个是不是排它锁,看方法名就知道其大概的内容。
这里的排它锁就是写锁。所以说,如果是想获得写锁的线程的话,就不插队了。如果是想获得读锁的线程的话就插队。因为在一个线程池中能运行的线程是有限的(这是自己理解,比如,对我自己电脑
来说,16线程,就算有很多读锁的线程,也需要在队列中排队,正常只能让16个线程在线程池中进行执行)
下面进行的是代码演练

package lock.readwrite;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CinemaReadWrite {
    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    //生成一个读锁
    private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    //生成一个写锁
    private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    private static void read(){
        readLock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            System.out.println(Thread.currentThread().getName() + "释放了读锁");
            readLock.unlock();
        }
    }

    private static void write(){
        writeLock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写数据");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            System.out.println(Thread.currentThread().getName() + "释放了写锁");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(() -> write(),"Thread1").start();
        new Thread(() -> read(),"Thread2").start();
        new Thread(() -> read(),"Thread3").start();
        new Thread(() -> write(),"Thread4").start();
        new Thread(() -> read(),"Thread5").start();
    }
}

在这里插入图片描述
结果如下:当第3个线程在读的过程中,看看第五个线程有没有读,由于,第四个线程是写,在排队的第一个线程是写锁的线程,所以,它就只能是等待了,不能插队。
注意:在非公平策略下,读锁在读取数据的过程中,如果有读锁开始获取,如果前面队列中的第一个是写锁,那么它是不能插队的,但是,如果是读锁。是可以插队的。
其实模拟这个还是比较困难的,因为理论上,当第一个为读锁的时候,它是很快就去读的,我们需要在它从获取锁的队列中激活的时间中,去获取到,才可以插队成功。
下面用代码进行模拟:

package lock.readwrite;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CinemaReadWrite {
    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    //生成一个读锁
    private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    //生成一个写锁
    private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    private static void read(){
        System.out.println(Thread.currentThread().getName() +"准备获取读锁");
        readLock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            System.out.println(Thread.currentThread().getName() + "释放了读锁");
            readLock.unlock();
        }
    }

    private static void write(){
        writeLock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写数据");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            System.out.println(Thread.currentThread().getName() + "释放了写锁");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(() -> write(),"Thread1").start();
        new Thread(() -> read(),"Thread2").start();
        new Thread(() -> read(),"Thread3").start();
        new Thread(() -> write(),"Thread4").start();
        new Thread(() -> read(),"Thread5").start();
        new Thread( new Runnable() {
            @Override
            public void run() {
                Thread thread[] = new Thread[1000];
                for (int i = 0; i < 1000; i++) {
                    thread[i] = new Thread(() -> read(),"子线程创建的Thread"  + i);
                }
                for (int i = 0; i < 1000; i++) {
                    thread[i].start();
                }
            }
        }).start();
    }
}

在这里插入图片描述
当Thread1释放了写锁的时候,其实在锁队列中排着的第一个是Thread2这个需要的是一个读锁,这个时候,在线程1释放写锁以后,其实子线程创建的Thread264准备获取读锁,其实它现在处于激活状态,所以,在非公平的策略下,它可以进行插队。
如果是公平锁的话,就完全按照队列来进行分配锁。
在这里插入图片描述
作用的引入:比如说我有一个任务,刚开始的时候是写入,后来的时候是读取,这时候我不希望一直是写锁的状态。这样浪费资源。如果可以支持降级,从我的写锁拿到我的读锁,再把我的写锁释放掉,这样就提高了整体的效率。支持锁的降级但是不可以升级,因为降级简单。
代码如下:

package lock.readwrite;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Upgrading {
    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true);
    //生成一个读锁
    private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    //生成一个写锁
    private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    private static void readUpgrading(){
        readLock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
            try {
                System.out.println("升级会带来阻塞");
                Thread.sleep(1000);
                writeLock.lock();
                System.out.println(Thread.currentThread().getName() + "获取到了写锁,升级成功");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            System.out.println(Thread.currentThread().getName() + "释放了读锁");
            readLock.unlock();
        }
    }

    private static void writeDowngrading(){
        writeLock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写数据");
            try {

                Thread.sleep(1000);
                readLock.lock();
                System.out.println("在不释放写锁的情况下,直接获得读锁,成功降级");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            readLock.unlock();
            System.out.println(Thread.currentThread().getName() + "释放了写锁");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
       new Thread(()->writeDowngrading(),"Thread1").start();
       
       
       new Thread(()-> readUpgrading(),"Thread2").start();
    }
}

结论如下:
在这里插入图片描述
在这里插入图片描述
因为不能又读又有写,如果我们想升级为写,要求其他的运行读锁的线程执行完毕。
如果两个拥有读锁的线程都想升级,那么它们就得等别的线程先把读锁释放,它们就得等,这时候线程A就会等线程B,线程B也会等着线程A 那么它们就会构成死锁。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
它会一直执行,直到修改成功。
在这里插入图片描述
自己写一个简单的自旋锁
代码如下:

package lock.readwrite;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁
 */
public class SpinLock {
    private AtomicReference<Thread> sign = new AtomicReference<>();
    public void lock(){
        Thread current = Thread.currentThread();
        while (!sign.compareAndSet(null,current)){
            System.out.println("自旋获取失败,再次尝试");
        }
    }
    public void unlock(){
        Thread current = Thread.currentThread();
        sign.compareAndSet(current,null);

    }

    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "开始尝试获取自旋锁");
                spinLock.lock();
                System.out.println(Thread.currentThread().getName() + "获取到了自旋锁");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    spinLock.unlock();
                    System.out.println(Thread.currentThread().getName() +  "释放了自旋锁");
                }
            }
        };
        Thread thread = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread.start();
        thread2.start();
    }
}

结果如下:
在这里插入图片描述
这里可以看到,线程1获取到了自旋锁,但是,线程0获取不到。所以,可以看到线程0中会一直在判断,判断它能不能获取到,其cpu是一直在工作的。
在这里插入图片描述
但判断到,线程1释放了自旋锁以后,线程0才开始获得自旋锁。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第一种:Java虚拟机对锁的优化
在这里插入图片描述
自旋锁和自适应:在尝试自旋锁的时候,比如说尝试10或者20次以后,虚拟机就会把锁转为阻塞锁
锁消除:就是说它检测到有一些锁本身就已经是安全的,这时候就会把锁进行消除。
锁粗化:指的是比如说synchronized 加锁的是两个对象,那么它就会把他合成一个。这样就是锁粗化。
在这里插入图片描述
第二点的原因:由于你方法可能内容会比较多,而且方法以后可能还会加东西,所以,最好不要锁住方法。
第三点:十个线程都想用写的操作,改进以后把想写的内容都放在了一个线程上,进行写,这样就只要加锁一次。
在这里插入图片描述
热点就是需要加锁的数据,避免人为制造热点的意思是:比如说hashMap 的size() 我需要知道map中的数量。那么如果调用size就需要给它加锁,这个时候我们就可以取一个变量,然后,当hashMap中加1的时候,给他加1。这样就给它进行加1了。少了一个热点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值