目录
Lock 的由来
线程之间的交互
Lock 方法简介
- lock()
- lockInterruptibly()
- trylock()
- trylock(long,TimeUnit)
- unlock()
- newCondition()
使用
Lock 保障高并发
Lock 期间线程挂起
LockRunnable 改造
ReentrantLockDemo 改造
总结
# 加入战队
微信公众号
大数据时代随之而来的就是并发问题. Java 开发本身提供了关于锁的操作. 我们知道的有 Synchronized. 这个是 JVM 层面的锁. 操作简单
Lock 的由来
因为 Synchronized 简单所以不可控制, 或者说不是很灵活. Synchronized 是已块进行执行加锁的. 这个时候我们需要通过 Lock 进行更加灵活的控制.
我们通过 tryLock , unLock 方法进行上锁释放锁.
线程之间的交互
在多线程开发中有的时候我们一个线程需要进行等待, 休眠操作. 这个时候其他线程没必要一直等待. Java 中提供了对应的方法进行线程切换
- | await/wait | sleep | yield |
---|---|---|---|
释放锁 | 释放 | 不释放 | 不释放 |
就绪节点 | notify/notifyall 方法后 | 休眠时间后 | 立刻就绪 |
提供者 | Object/Condition | Thread | Thread |
代码位置 | 代码块 | 任意 | 任意 |
通过上述表格我们可以看出来. 在线程中我们可以通过 Object.wait 方法或者 Condition.wait 方法进行线程挂起的等待 (将资源让给其他线程). 在其他线程通过 Object.notify,Object.notifyall , Condition.signal 方法进行唤醒当前挂载的线程 (当前挂载的线程不止一个).
Object.notify | Object.notifyall | Condition.signal |
---|---|---|
随机唤醒挂载线程之一 | 随机唤醒挂载线程之一 | 按顺序唤醒当前 condition 上的挂载线程 |
这里主要区别是 Object 和 Condition 两个类. Condition.signal 会通知相同 Condition 上的线程就绪 (按序通知)
Lock 方法简介
通过查看源码我们发现 Lock 下方法如上. 下面我们简单介绍下方法功能
lock()
当前线程对资源进行上锁操作.(如果已被上锁会一直阻塞住. 一直到获取到锁). 为什么避免死锁的发生, 建议在 try,catch,finally 中结合使用. 保证在 finally 中一定会对资源的释放
lockInterruptibly()
顾名思义就是打断锁, 在我们对资源进行加锁被占用是进行等待时, 我们可以通过 interrupt() 方法打断在阻塞的线程.
trylock()
trylock 就是尝试去加锁, 如果资源被锁则返回 false, 否则返回 true 表示加锁成功.
trylock(long,TimeUnit)
尝试加锁是被占用, 通过 TimeUnit 指定等待时间段. 超时后返回 false
unlock()
unlock 就是去释放锁占用的锁. 在 finnally 中释放. 使用是一定要让代码走到释放锁的地方. 避免死锁.
newCondition()
和 Object 的 notify 不同的是. newCondition 会创建一个 Condition 将与此线程进行绑定. 这里可以理解为不同的线程绑定在同一个 Condition 上是一队列的方式绑定的. 当 Condition.signal 方法是, 会从该队列中取出头部的线程进行唤醒就绪.
使用
通过查看 Lock 的引用关系得治, JDK 中锁都是继承 Lock 实现的. 使用最多的应该是 ReentrantLock(可重入式锁) . 什么叫可重入式锁呢就是一个线程可以多次调用 lock 方法, 对应需要多次调用 unlock 进行解锁.
Lock 保障高并发
源码位置
- package com.GitHub.zxhtom.lock;
- import lombok.Data;
- import java.util.concurrent.locks.Lock;
- /**
- * @author 张新华
- * @version V1.0
- * @Package com.GitHub.zxhtom.lock
- * @date 2020 年 07 月 09 日, 0009 14:30
- * @Copyright © 2020 安元科技有限公司
- */
- public class Counter {
- private static Counter util = new Counter();
- public static Counter getInstance(){
- return util;
- }
- private int index;
- public static Counter getUtil() {
- return util;
- }
- public static void setUtil(Counter util) {
- Counter.util = util;
- }
- public int getIndex() {
- return index;
- }
- public void setIndex(Lock lock , int index) {
- /* 这里加锁解锁是为了显示可重入性, 在外部为加锁解锁 */
- lock.lock();
- this.index = index;
- lock.unlock();
- }
- }
- package com.GitHub.zxhtom.lock;
- import java.util.Random;
- import java.util.concurrent.locks.Lock;
- /**
- * @author 张新华
- * @version V1.0
- * @Package com.GitHub.zxhtom.lock
- * @date 2020 年 07 月 09 日, 0009 14:19
- * @Copyright © 2020 安元科技有限公司
- */
- public class LockRunnable implements Runnable {
- private Lock lock;
- public LockRunnable(Lock lock ) {
- this.lock = lock;
- }
- @Override
- public void run() {
- try {
- Thread.sleep(new Random().nextInt(10));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- /*lock,unlock 之间的业务就能保证同一时刻只有一个线程访问. 前提 * 是同一个 lock 对象 , setIndex 中也有 lock 程序正常运行说明可重 * 入
- */
- this.lock.lock();
- Counter instance = Counter.getInstance();
- instance.setIndex(this.lock,instance.getIndex()+1);
- this.lock.unlock();
- }
- }
- package com.GitHub.zxhtom.lock;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Random;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- * @author 张新华
- * @version V1.0
- * @Package com.GitHub.zxhtom.lock
- * @date 2020 年 07 月 01 日, 0001 14:24
- * @Copyright © 2020 安元科技有限公司
- */
- public class ReentrantLockDemo {
- public static void main(String[] args) {
- ReentrantLock lock = new ReentrantLock();
- List<Thread> threadList = new ArrayList<>();
- for (int i = 0; i <1000; i++) {
- int finalI = i;
- Thread thread = new Thread(new LockRunnable(lock));
- thread.start();
- threadList.add(thread);
- }
- for (Thread thread : threadList) {
- try {
- thread.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- System.out.println(Counter.getInstance().getIndex());
- }
- }
上述代码体现了 ReentranLock 的可重入性, 另外也保障了高并发的问题. 如果我们将 LockRunnable 中的加锁解锁去掉在运行输出的结果就会少于 1000. 在 Counter 中的加锁解锁不去也是会少的. 因为那里的加锁解锁只是为了测试可重入性. 因为在 LockRunnable 中的是 get,set 结合使用的. 所以仅仅对 set 加锁没有用的.
Lock 期间线程挂起
上面已经实现了高并发场景下加锁等待执行了. 但是现在我们有一个这样的场景
场景: 1000 个线程按名字的奇偶性分组, 奇数一组, 偶数一组. 奇数执行完之后需要将锁传递给同组的线程 .
根据上述场景我们先考虑一下, 第一个执行的线程和最后一个执行的线程. 第一个线程毫无疑问是随机争取. 而最后一个肯定是第一个同组内的最后一个. 那么剩下的一组只能等待前一组全部执行完毕在执行了
在开发奇偶分组的场景需求时, 我们先回顾下上面的高并发的代码.,
在介绍 lock 方法是我着重强调了 unlock 方法正常需要在 try catch finally 的 finally 中执行. 但是为什么我是直接这样开发. 这里其实是小编开发时大意了. 后来想着正好能起一个反面作用. 我们上面也看到了不在 finally 中执行也是可以的. 但是在接下来 Condition 环境下不在 finally 中 unlock 就会导致线程 hold on 了.
LockRunnable 改造
LockRunnalble 构造函数里多接受了 Condition 类, 这个类就是用来分组的. 在 run 方法中我们首先去抢占锁, 抢到锁就将线程挂起 (condition 挂起)condition.await(). 这样线程就会处于等待状态. 结合 Demo 类中所有线程都会处于 awaitting 状态. await 阻塞现场后 finally 里的也不会被执行. 因为线程被阻塞整体都不会再运转了. 我们在 ReentrantLockDemo 类中会通过 Condition 进行分组唤醒. 唤醒的线程执行 await 后面的代码. 执行完进行同组线程唤醒并释放锁. 这样就能保证线程是分组执行的.
- package com.GitHub.zxhtom.lock;
- import java.util.Random;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- /**
- * @author 张新华
- * @version V1.0
- * @Package com.GitHub.zxhtom.lock
- * @date 2020 年 07 月 09 日, 0009 14:19
- * @Copyright © 2020 安元科技有限公司
- */
- public class LockRunnable implements Runnable {
- private Lock lock;
- private Condition condition;
- private int index;
- public LockRunnable(Lock lock , Condition condition,int index) {
- this.lock = lock;
- this.condition = condition;
- this.index = index;
- }
- @Override
- public void run() {
- try {
- this.lock.lock();
- //if (index != 0) {
- condition.await();
- //}
- System.out.println(Thread.currentThread().getName());
- Counter instance = Counter.getInstance();
- instance.setIndex(this.lock,instance.getIndex()+1);
- condition.signal();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }finally {
- this.lock.unlock();
- }
- }
- }
ReentrantLockDemo 改造
在构建线程的时候传入的 Condition 是按照序号进行传递的. 我们提前准备了两个 Condition. 一个用来存放奇数好线程 (oddCondition). 一个是存储偶数号线程 (evenCondition).
线程创建好之后, 这个时候由于 LockRunnable 中 condition.await 方法早成线程阻塞了. 后面我们通过不同的 Condition 进行同组线程唤醒. 在所有线程结束后我们打印执行数也是 1000. 我在 LockRunnable 代码中输出了当前线程名字. 我们通过日志发现是 oddConditon(奇数条件) 线程先输出的. 50 个奇数执行完了才开始 evenCondition(偶数条件). 这是因为我们先 oddCondition.signal 的. 这里读者可以自行执行代码看效果. 小编试了试日志输出是分组输出的.
在奇偶添加 signal 的时候间隔时间一定要足够长. 因为在释放锁的时候如果这个时候 condition 前面的 lock 会抢锁这样的话就不会是分组了. 因为我们为了测试所以这里要足够长
- package com.GitHub.zxhtom.lock;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Random;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- * @author 张新华
- * @version V1.0
- * @Package com.GitHub.zxhtom.lock
- * @date 2020 年 07 月 01 日, 0001 14:24
- * @Copyright © 2020 安元科技有限公司
- */
- public class ReentrantLockDemo {
- public static void main(String[] args) {
- ReentrantLock lock = new ReentrantLock();
- /* 奇数 */
- Condition oddCondition = lock.newCondition();
- /* 偶数 */
- Condition evenCondition = lock.newCondition();
- List<Thread> threadList = new ArrayList<>();
- for (int i = 0; i < 1000; i++) {
- int finalI = i;
- Condition condition = null;
- if (i % 2 == 0) {
- condition = evenCondition;
- } else {
- condition = oddCondition;
- }
- Thread thread = new Thread(new LockRunnable(lock,condition,i));
- thread.start();
- threadList.add(thread);
- }
- try {
- lock.lock();
- oddCondition.signal();
- }finally {
- lock.unlock();
- }
- try {
- /* 休眠足够长, 目的是不与前面队列抢锁. 可以调更长时间.
- * 这样测试准确 */
- Thread.sleep(10000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- try {
- lock.lock();
- evenCondition.signal();
- }finally {
- lock.unlock();
- }
- for (Thread thread : threadList) {
- try {
- thread.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- System.out.println(Counter.getInstance().getIndex());
- }
- }
总结
我们通过 Lock 的 lock,unlock 就可以灵活的控制并发执行顺序. 上面第二个列子如果我们不在 finally 中执行 unlock 就会带了很多意想不到的效果, 读者可以自己放在一起执行看看效果 (在第二个列子中试试). 第一个放在一起没问题是因为业务简单没有造成问题的.
Condition 条件队列, 不同的 Condition 调用 await 相当于将当前线程绑定到该 Condition 上. 当 Condition 唤醒线程内部会将 Condition 队列等待的节点转移到同步队列上, 这里也是为什么上面提到两个 Condition 间隔时间需要足够长. 因为 Condition 唤醒队列上等待的线程实际上不是真正的唤醒而是件线程添加到通过队列上, 借由同步队列的活跃机制唤醒线程的, 如果间隔时间不长这个时候回去和刚刚 Condition 添加过来的线程进行抢锁的. Condition 唤醒实际上就是重新竞争一把锁.
加入战队
# 加入战队
微信公众号
来源: https://www.cnblogs.com/zhangxinhua/p/13320781.html