03-JUC-Lock

Lock接口

锁是一种工具,用于控制对共享资源的访问。
Lock和synchronized,是两种最常见的锁,它们都可以达到线程安全的目的,但在使用上和功能上有较大的不同。
Lock不是 synchronized的代替品,而是当使用sychronized不合适或不足以满足要求的时候,才使用来提供较为高级的功能的。
Lock接口最常见的实现类是ReentrantLock
通常情况下,Lock只允许一个线程来访问被锁住的共享资源。不过有些时候,一些特俗的实现也可以允许并发访问,比如ReadWriteLock里面的ReadLock。

为什么需要Lock?

为什么synchronized不够用?

  1. 效率低:锁的释放情况少,试图获取锁时不能设定超时,不能中断一个正在试图获得锁的线程。
  2. 不够灵活:加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的。
  3. 无法知道synchronized是否成功获取到锁

Lock的主要接口

在这里插入图片描述

lock()

在这里插入图片描述ReentrantLock中的lock实现

在这里插入图片描述

代码演示


/**
 * @Classname MustUnlock
 * @Description  Lock不会像synchronized一样,异常的时候自动释放锁,所以最佳时间是,finally中释放锁,保证异常的时候锁一定被释放
 * @Date 2021/2/17 13:28
 * @Created by YoungLiu
 */
public class MustUnlock {
    
    
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args){
    
    
        lock.lock();
        try{
    
    
            //获取本锁保护的资源
            System.out.println(Thread.currentThread().getName()+"开始执行任务");
        }finally {
    
    
            lock.unlock();
        }
    }
}

tryLock()

在这里插入图片描述
在这里插入图片描述
还有另外一个重载;这个重载会等一段时间,如果超时就放弃获取锁的行为。
在这里插入图片描述

代码演示1:tryLock避免死锁


/**
 * @Classname TryLockDeadlock
 * @Description 用TryLock避免死锁
 * @Date 2021/2/17 13:39
 * @Created by YoungLiu
 */
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=2;
        new Thread(r1).start();
        new Thread(r2).start();
    }

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


                            if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
    
    
                                try {
    
    
                                    System.out.println("线程1获取到了锁2");
                                    System.out.println("线程1成功获取到了两把锁");
                                    break;
                                } finally {
    
    
                                    lock2.unlock();
                                    System.out.println("线程1释放了锁2");
                                }
                            }else{
    
    
                                System.out.println("线程1获取锁2失败,已重试");
                            }

                        } finally {
    
    
                            lock1.unlock();
                            System.out.println("线程1释放了锁1");
                            Thread.sleep(1000);
                        }
                    } else {
    
    
                        System.out.println("线程1获取锁1失败,已重试");
                    }
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }

            if (flag == 2) {
    
    
                try {
    
    
                    if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
    
    
                        try {
    
    
                            System.out.println("线程2获取到了锁1");
                            Thread.sleep(500);


                            if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
    
    
                                try {
    
    
                                    System.out.println("线程2获取到了锁2");
                                    System.out.println("线程2成功获取到了两把锁");
                                    break;
                                } finally {
    
    
                                    System.out.println("线程2释放了锁2");
                                    lock2.unlock();
                                }
                            }else{
    
    
                                System.out.println("线程2获取锁2失败,已重试");
                            }

                        } finally {
    
    
                            lock1.unlock();
                            System.out.println("线程2释放了锁1");
                            Thread.sleep(500);
                        }
                    } else {
    
    
                        System.out.println("线程2获取锁1失败,已重试");
                    }
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }

        }
    }
}

lockInterruptibly()

在这里插入图片描述

代码演示


/**
 * @Classname LockInterruptibly
 * @Description TODO
 * @Date 2021/2/17 13:54
 * @Created by YoungLiu
 */
public class LockInterruptibly implements Runnable {
    
    

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
    
    
        LockInterruptibly lockInterruptibly = new LockInterruptibly();
        Thread t0 = new Thread(lockInterruptibly);
        Thread t1 = new Thread(lockInterruptibly);

        t0.start();
        t1.start();

        try {
    
    
            Thread.sleep(2000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        t1.interrupt();
    }
    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName() + " 尝试获取锁");

        try {
    
    
            //此方法若获取不到锁,会一直等待;若等待期间被中断,触发会触发InterruptedException
            lock.lockInterruptibly();
            try{
    
    
                System.out.println(Thread.currentThread().getName()+" 获取到了锁");
                //在睡眠期间被中断,会触发InterruptedException
                Thread.sleep(5000);
            }catch (InterruptedException e){
    
    
                System.out.println("睡眠期间被中断");
            }finally{
    
    
                lock.unlock();
                System.out.println(Thread.currentThread().getName()+" 释放了锁");
            }
        } catch (InterruptedException e) {
    
    
            System.out.println("等锁期间被中断");
        }
    }
}

lock的可见性保证

在这里插入图片描述

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

锁的分类

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

乐观锁和悲观锁

悲观锁

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

乐观锁

在这里插入图片描述

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

可重入锁和非可重入锁 以ReentrantLock为例

https://segmentfault.com/a/1190000022408592 参考此博文

可重入性质

可重入锁也叫递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。

代码演示
public class Recursion {
    
    
    private static ReentrantLock lock = new ReentrantLock();
    private static void accessResource(){
    
    
        lock.lock();
        try{
    
    
            System.out.println("已经对资源进行了处理");
            if(lock.getHoldCount()<5){
    
    
                System.out.println(lock.getHoldCount());
                accessResource();
                System.out.println(lock.getHoldCount());
            }
        }finally {
    
    
            lock.unlock();
        }
    }

    public static void main(String[] args) {
    
    
        accessResource();
    }
}
源码分析

在这里插入图片描述

公平锁和非公平锁

公平指的是按照线程请求的顺序,来分配锁;
非公平指的是,不完全按照请求的顺序,在一定的情况下,可以插队。
notice:非公平也同样不提倡“插队”行为,这里的非公平,指的是“在合适的时机”插队,而不是盲目插队。
在这里插入图片描述
在这里插入图片描述

代码演示

/**
 * @Classname FairLock
 * @Description  show fair and not fair lock.
 * @Date 2021/2/17 15:18
 * @Created by YoungLiu
 */
public class FairLock {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        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();
            Thread.sleep(100);
        }
    }


}

class Job implements  Runnable{
    
    
    PrintQueue printQueue ;

    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{
    
    
    //参数代表  fair or not fair
//    private Lock queueLock = new ReentrantLock(true);
    private Lock queueLock = new ReentrantLock(false);

    public void printJob(Object document){
    
    
        queueLock.lock();
        try{
    
    
            Long duration = (long)(new Random().nextInt(10)+1);
            System.out.println(Thread.currentThread().getName()+" 正在打印,需要"+duration);
            Thread.sleep(duration*1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            queueLock.unlock();
        }

        queueLock.lock();
        try{
    
    
            Long duration = (long)(new Random().nextInt(10)+1);
            System.out.println(Thread.currentThread().getName()+" 正在打印,需要"+duration);
            Thread.sleep(duration*1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            queueLock.unlock();
        }
    }
}

优缺点

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

源码分析

在这里插入图片描述

共享锁和排他锁 以Reentrant为例

排他锁,又称为独占锁,独享锁;

共享锁,又称为读锁,获得共享锁之后,可以查看但是无法修改和删除数据,其他线程此时也可以获取到共享锁,也可以查看,但无法修改和删除。

共享锁和排他锁的典型是读写锁ReentrantReadWriteLock,其中ReadLock是共享锁,WriteLock是排他锁。

读写锁的作用

在这里插入图片描述

读写锁的规则

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

代码演示

程序的输出,显然能够同时读,但不能够同时写。
在这里插入图片描述


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()+" 得到了读锁,正在读取");
            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()+"得到了写锁,正在写入");
            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();
    }
}

ReentrantReadWriteLock的实现

ReentrantReadWriteLock 在非公平的情况下,不允许读锁插队,以此来避免饥饿发生;
在升降级方面,允许升级,不允许降级。

插队策略

公平策略下的源码:可以看到都要进入队列缓冲。
在这里插入图片描述
非公平的情况下:可以看到注释,写锁总是可以插队的。
在这里插入图片描述
在非公平策略下,读锁在某种情况下可以插队:看此方法的名字:apparentlyFirstQueuedIsExclusive(); 队列头是否是排它锁(此处为写锁);如果为排他锁则不可以插队,返回true;若队头为共享锁(此处为读锁),则可以插队,返回false。

在这里插入图片描述

锁的升级和降级

在某些操作中可能一开始获取的是写锁,但完成某一些操作后,便不再需要写入,如果继续持有写锁会造成资源的浪费,所以要降级为读锁。

降级:在持有WriteLock时,再次申请ReadLock,就将此线程持有的锁降级为readlock。
升级:在持有readlock时,再次申请writelock,将此线程持有的锁升级为writelock,但这是不被支持的。

在此处只支持锁的降低,不支持升级。

代码演示

public class Upgrading {
    
    

    private static ReentrantReadWriteLock reentrantReadWriteLock =
            new ReentrantReadWriteLock();
    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()+" 得到了读锁,正在读取");
            Thread.sleep(1000);
            System.out.println("升级会带来阻塞");
            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()+"得到了写锁,正在写入");
            Thread.sleep(1000);
            System.out.println("开始降级");
            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) {
    
    
//        System.out.println("演示降级");
//        new Thread(()->writeDowngrading(),"Thread1").start();
//        System.out.println("-------------------------");
        System.out.println("演示升级是不行的");
        new Thread(()->readUpgrading(),"Thread2").start();
    }
}

共享锁和排他锁的总结

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

自旋锁和阻塞锁

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

自旋锁的缺点

在这里插入图片描述

原理和源码分析

CAS:Compare and Swap,即比较再交换,一种实现并发算法时常用到的技术。

有兴趣可以阅读wiki:https://zh.wikipedia.org/wiki/%E6%AF%94%E8%BE%83%E5%B9%B6%E4%BA%A4%E6%8D%A2
在这里插入图片描述

代码演示

public class SpinLock {
    
    
    private AtomicReference<Thread> sign= new AtomicReference<>();

    public void lock(){
    
    
        //得到当前线程的引用
        Thread current = Thread.currentThread();
        while(!sign.compareAndSet(null,current)){
    
    
            System.out.println(Thread.currentThread().getName()+"本次尝试获取自旋锁失败");
        }
    }

    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(300);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    spinLock.unlock();
                    System.out.println(Thread.currentThread().getName()+"释放了自旋锁");
                }
            }
        };
        Thread t0 = new Thread(runnable);
        Thread t1 = new Thread(runnable);
        t0.start();
        t1.start();
    }
}

自旋锁的适用场景

在这里插入图片描述

JVM对锁的优化

JVM会对锁进行优化,可自行搜索相关阅读材料i。

如何较为正确的使用lock

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

猜你喜欢

转载自blog.csdn.net/qq_41729287/article/details/113833394