java ReentrantLock 锁 await、signal的用法

背景

在并发编程中,为了保证线程的原子执行,需要使用锁,jvm 内 可以使用 synchronized 和 ReentrantLock,如果是集群部署,我们可以使用Redis 分布式锁 其他的锁后面再介绍。

ReentrantLock 和 synchronized

1、ReentrantLock 通过方法 lock()与 unlock()来进行加锁与解锁操作,与synchronized(1.8之后性能得到提升)会被JVM自动解锁机制不同,ReentrantLock 加锁后需要手动进行解锁

2、ReentrantLock 相比 synchronized 的优势是可中断、公平锁、多个锁

简述

ReentrantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完
成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等
避免多线程死锁的方法

Lock 在 java.util.concurrent.locks

ReentrantLock 实现了 Lock 接口,拥有下面几个方法:

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;

public interface Lock {

    //执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁. 相反, 如果锁已经
被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁
    void lock();

    //如果当前线程未被中断,获取锁
    void lockInterruptibly() throws InterruptedException;

    //如果锁可用, 则获取锁, 并立即返回 true, 否则返回 false
    boolean tryLock();

   
    //如果锁在给定等待时间内没有被另一个线程保持,则获取该锁并返回 true,否则返回 false。
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

   //执行此方法时, 当前线程将释放持有的锁. 锁只能由持有者释放, 如果线程
并不持有锁, 却执行该方法, 可能导致异常的发生
    void unlock();

    //条件对象,获取等待通知组件
    Condition newCondition();
}

初始化 锁

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

public class LockTest {

    Lock lock = new ReentrantLock();

    Condition condition = lock.newCondition();
}

说明

java ReentrantLock 锁 await、signal的用法_第1张图片

1、同一时刻只有一个线程能获取独占锁(lock.lock()),持有了这个锁的人才能调用condition.await()和 condition.sign()方法。


2、调用 condition.await()方法会释放独占锁(lock.unlock),并且将自身加入到condition队列中。


3、Thread-signal-1线程获取lock 后,condition.signal()方法不会立即唤醒await中的线程,而是将condition队列中的线程转移到AQS队列中。


4、当持锁线程释放锁时,AQS队列的线程再抢到则会被唤醒。

线程池中使用锁

1、我们使用线程池 来演示 如何使用 ReentrantLock,首先创建一个 固定的线程池

ExecutorService executor = Executors.newFixedThreadPool(10);

2、发起10个线程的并发处理,这里使用for 循环来提交 线程到线程池

3、每个线程 实现 Runnable 接口的run 方法

4、run方法中尝试获取锁,等待2秒,获取到锁后,休眠10秒,然后调用await 方法,在调用await方法时,当前线程会把自身加入到condition中,同时释放 lock 锁(当然这里也有部分线程获取不到锁)。

5、run 方法执行完成后,本次请求结束。

6、请求unlock路由,这时线程会去尝试获取 lock , 然后 调用 condition.signal() 方法 来唤起刚刚通过 condition.await() 加入到condition 队列中的线程,当lock.unlock() 后,会执行刚刚加入condition队列中线程的剩余代码并打印 "signal-> await 执行剩余的代码:pool-4-thread-1"。

package com.yd.controller.user;

import com.yd.entity.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@RestController
@RequestMapping("/admin/user")
public class LockTest {

    Lock lock = new ReentrantLock();

    Condition condition = lock.newCondition();

    @GetMapping("exec")
    public void execs() {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        if (lock.tryLock(2, TimeUnit.SECONDS)) {
                            System.out.println("获取到锁:" + Thread.currentThread().getName());
                            TimeUnit.SECONDS.sleep(10);
                            condition.await(); //此处会将当前线程放到队列condition,并释放lock锁,其余线程才能获取到锁,但下面的代码不会被执行,只有当signal() 方法被调用时,才会打印下面的代码
                            System.out.println("signal-> await 执行剩余的代码:" + Thread.currentThread().getName());
                            lock.unlock();
                        } else {
                            System.out.println("未获取锁" + Thread.currentThread().getName());
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
        executor.shutdown();
    }

    @GetMapping("unlock")
    public ResponseEntity  unlock() {

        lock.lock();
        condition.signalAll();
        lock.unlock();
        return ResponseEntity.success("yes");
    }
}

启动springboot 后,访问

curl -X GET http://127.0.0.1:8089/admin/user/exec

执行结果如下,只有 thread-1 获取到锁,其余线程均未获取到锁

获取到锁:pool-4-thread-1
未获取锁pool-4-thread-2
未获取锁pool-4-thread-7
未获取锁pool-4-thread-4
未获取锁pool-4-thread-5
未获取锁pool-4-thread-6
未获取锁pool-4-thread-10
未获取锁pool-4-thread-3
未获取锁pool-4-thread-8
未获取锁pool-4-thread-9

接下来访问

curl http://127.0.0.1:8089/admin/user/unlock

执行结果如下,会将上面 thread-1 加入到condition 队列中的剩余代码执行完毕。

signal-> await 执行剩余的代码:pool-4-thread-1

注意事项

在单元测试中,想要看到锁的效果,需要在代码后面加一个 睡眠提醒(Thread.sleep(10000),主线程结束后,锁的运行逻辑表现不出来。

在springboot中,主线程默认是后台运行的,没有影响。

Semaphore 信号量

       

Semaphore 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信
号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore 可以用来
构建一些对象池,资源池之类的,比如数据库连接池。

Semaphore 基本能完成 ReentrantLock 的所有工作,使用方法也与之类似,通过 acquire()与
release()方法来获得和释放临界资源。

在springboot中使用单元测试,直接上代码:

 @Test
    public void testSem() throws InterruptedException {
        Semaphore semaphore = new Semaphore(5);
        ExecutorService executor = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();
                        Thread.sleep(4000);
                        //DateFormatUtils 使用时,需要引入common-lang3 包
                        System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + "获取到锁");
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } finally {
                        semaphore.release();
                    }
                }
            });
        }
        executor.shutdown();
        //让主线程挂起,否则程序直接退出
        Thread.sleep(20000);
    }

执行结果如下, 开启10个线程来执行,每次最多5个线程获取到锁

2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁


2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁

你可能感兴趣的:(高并发,java,java,开发语言)