【Java】JUC并发编程-Lock锁

目录

一、概述

Lock锁是一个类似于Synchronized的线程同步机制,但是Lock比Synchronized更加灵活。Lock是个接口,有两个实现类:ReentrantLock(重入锁)、ReentrantReadWriteLock(读写锁)

二、Lock和Synchronized的区别

  1. lock是一个接口,synchronized是Java中的关键字
  2. synchronized不需要用户去手动释放锁,发生异常或者线程结束时自动释放锁;lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象
  3. lock可以让等待锁的线程响应中断,使用synchronized时,等待的线程会一直等待下去,不能够响应中断

三、Lock锁的API

四、ReentrantLock(重入锁)、ReentrantReadWriteLock(读写锁)

1、ReentrantLock(重入锁)

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

java 复制代码
public class ReentrantDemo implements Runnable {
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        set();
    }
    public void set() {
        try {
            lock.lock();
            System.out.println("set 方法");
            get();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();// 必须在finally中释放
        }
    }

    public void get() {

        try {
            lock.lock();
            System.out.println("get 方法");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        ReentrantDemo reentrantDemo = new ReentrantDemo();
        new Thread(reentrantDemo).start();
    }

测试结果:同一个线程,首先在set方法中获取锁,然后调用get方法,get方法中重复获取同一个锁。两个方法都执行成功

2、ReentrantReadWriteLock(读写锁)

读写锁,可以分别获取读锁或写锁。也就是说将数据的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。读锁使用共享模式;写锁使用独占模式;读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁

  • writeLock():获取写锁。
  • readLock():获取读锁。

执行三个线程进行读写操作,并设置一个屏障,线程依次准备就绪后未获取锁之前都在等待,当第三个线程执行

cyclicBarrier.await()后屏障解除,三个线程同时执行。

java 复制代码
public class WriteAndReadLockTest {
    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
            60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
    private static int i = 100;
    public static void main(String[] args) {
        threadPoolExecutor.execute(()->{
            read(Thread.currentThread());
        });
        threadPoolExecutor.execute(()->{
            write(Thread.currentThread());
        });
        threadPoolExecutor.execute(()->{
            read(Thread.currentThread());
        });
        threadPoolExecutor.shutdown();
    }

    private static void read(Thread thread) {
        try {
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        reentrantReadWriteLock.readLock().lock();
        try {
            System.out.println("读线程 "+ thread.getName() + " 开始执行, i=" + i);
            Thread.sleep(1000);
            System.out.println(thread.getName() +" is over!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantReadWriteLock.readLock().unlock();

        }
    }
    private static void write(Thread thread) {
        try {
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        reentrantReadWriteLock.writeLock().lock();
        try {
            i++;
            System.out.println("写线程 "+ thread.getName() + " is doing, i=" + i);
            System.out.println(thread.getName() +" is over!");
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }
}

执行结果:线程3先获取到了读锁,因为读锁时是可以共享的,所以线程1也可以获取到读锁,线程1、线程3读操作完成后,将读锁释放后,线程2才能获取到写锁并开始执行写操作

五、Lock锁的API代码实现

1、lock()、unLock()

java 复制代码
public class LockAndUnlock  implements Runnable{
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        //上锁
        lock.lock();
        try {
            System.out.println("ThreadA获取锁");
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // 解锁
            System.out.println("ThreadA释放锁");
        }
    }

    public static void main(String[] args) {
        LockAndUnlock lockAndUnlock = new LockAndUnlock();
        Thread threadA = new Thread(lockAndUnlock,"threadA");
        threadA.start();
    }
}

执行结果:

2、lockInterruptibly()

实例:当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

java 复制代码
public class LockInterruptiblyDemo {
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lockInterruptibly();
                    System.out.println("ThreadA获得锁 ");
                    // 模拟线程A持有锁的一段时间
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    System.out.println("ThreadA在等待锁时被中断");
                } finally {
                    if (Thread.holdsLock(lock)) {
                        lock.unlock();
                    }
                }
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lockInterruptibly();
                    System.out.println("ThreadB获得锁");
                } catch (InterruptedException e) {
                    System.out.println("ThreadB在等待锁时被中断");
                } finally {
                    if (Thread.holdsLock(lock)) {
                        lock.unlock();
                    }
                }
            }
        });

        threadA.start();
        // 让线程A先获取到锁
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadB.start();

        // 在线程B等待锁的过程中,中断线程B
        threadB.interrupt();
    }
}

执行结果:

3、tryLock()

实例:当两个线程同时通过lock.tryLock()想获取某个锁时,假若此时线程A获取到了锁,而线程B不会等待,直接放弃获取锁

java 复制代码
public class TryLock{
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                if (lock.tryLock()) {
                    try {
                        System.out.println("ThreadA获得了锁");
                        // 模拟线程A持有锁的一段时间
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                } else {
                    System.out.println("ThreadA获取锁失败");
                }
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                if (lock.tryLock()) {
                    try {
                        System.out.println("ThreadB获得了锁");
                    } finally {
                        lock.unlock();
                    }
                } else {
                    System.out.println("ThreadB获取锁失败");
                }
            }
        });

        threadA.start();
        // 让线程A先获取到锁
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadB.start();
    }
}

执行结果:

4、tryLock(long time, TimeUnit unit)

如果锁定可用,则此方法立即返回值true。

如果锁不可用,则当前线程将被禁用以进行线程调度,并且在发生以下三种情况之一之前处于休眠状态:

● 当前线程获取锁。

● 其他一些线程中断当前线程。

● 等待时间过去了,返回false

java 复制代码
public class TryLockParam implements Runnable{
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            //假设 threadA 线程先持有锁, 完成任务需要 4 秒钟,
            //这个时候 threadB 线程尝试获得锁, threadB 线程在 3 秒内还没有获得锁的话, 那么它就不再等了,直接放弃
            if (lock.tryLock(3, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName() + "获得锁,执行耗时任务");
                Thread.sleep(1000 * 4);
            }else {
                System.out.println(Thread.currentThread().getName() + "放弃获得锁");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (Thread.holdsLock(lock)) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TryLockParam tryLockParam=new TryLockParam();

        Thread threadA=new Thread(tryLockParam,"threadA");
        Thread threadB=new Thread(tryLockParam,"threadB");

        threadA.start();
        threadB.start();
    }
}

执行结果:

如果锁不可用,则当前线程将被禁用以进行线程调度,并且在发生以下三种情况之一之前处于休眠状态,当前线程是可以被中断的。

java 复制代码
public class TryLockParam implements Runnable{
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            //假设 threadA 线程先持有锁, 完成任务需要 4 秒钟,
            //这个时候 threadB 线程尝试获得锁, threadB 线程在 3 秒内还没有获得锁的话, 那么它就不再等了,直接放弃
            if (lock.tryLock(3, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName() + "获得锁");
                for (int i = 0; i <= 500000000; i++) {
                    if (i == 500000000) {
                        System.out.println("运算结束");
                    }
                }
                lock.unlock();
            }else {
                System.out.println(Thread.currentThread().getName() + "放弃获得锁");
            }
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() +"中断");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        TryLockParam tryLockParam=new TryLockParam();

        Thread threadA=new Thread(tryLockParam,"threadA");
        Thread threadB=new Thread(tryLockParam,"threadB");

        threadA.start();
        threadB.start();


        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadA.interrupt();
        threadB.interrupt();
    }
}

执行结果:threadA获得锁,其他线程休眠,然后被中断

相关推荐
__ocean7 分钟前
编译Qt5.15.16并启用pdf模块
开发语言·qt·pdf
万物得其道者成8 分钟前
从零开始创建一个 Next.js 项目并实现一个 TodoList 示例
开发语言·javascript·ecmascript
向哆哆17 分钟前
Eclipse Java 开发调优:如何让 Eclipse 运行更快?
java·ide·eclipse
爱晒太阳的小老鼠24 分钟前
策略模式-枚举实现
java·策略模式
77tian31 分钟前
设计模式的原理及深入解析
java·开发语言·单例模式·设计模式·代理模式·享元模式·原型模式
幽蓝计划32 分钟前
仓颉开发语言入门教程:搭建开发环境
开发语言·鸿蒙
Dovis(誓平步青云)1 小时前
探索C++面向对象:从抽象到实体的元规则(上篇)
开发语言·c++·经验分享·笔记·学习方法
北极象1 小时前
各编程语言对正则表达式标准的支持对比
开发语言·正则表达式·scala·go语言
会飞的架狗师1 小时前
【Spring Cloud Gateway】Nacos整合遇坑记:503 Service Unavailable
java·开发语言
易只轻松熊2 小时前
C++(21):fstream的读取和写入
开发语言·c++