大数据时代随之而来的就是并发问题。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唤醒实际上就是重新竞争一把锁。

加入战队

# 加入战队

微信公众号

java的干儿子锁Lock的更多相关文章

  1. 深入浅出Java并发包—锁(Lock)VS同步(synchronized)

    今天我们来探讨一下Java中的锁机制.前面我们提到,在JDK1.5之前只能通过synchronized关键字来实现同步,这个前面我们已经提到是属于独占锁,性能并不高,因此JDK1.5之后开始借助JNI ...

  2. Java中的锁——Lock和synchronized

    上一篇Java中的队列同步器AQS 一.Lock接口 1.Lock接口和synchronized内置锁 a)synchronized:Java提供的内置锁机制,Java中的每个对象都可以用作一个实现同 ...

  3. Java并发指南4:Java中的锁 Lock和synchronized

    Java中的锁机制及Lock类 锁的释放-获取建立的happens before 关系 锁是java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消 ...

  4. Java 中的锁——Lock接口

    Java SE5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能.虽然它少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的操作性. ...

  5. 【Java并发】锁机制

    一.重入锁 二.读写锁 三.悲观锁.乐观锁 3.1 悲观锁 3.2 乐观锁 3.3 CAS操作方式 3.4 CAS算法理解 3.5 CAS(乐观锁算法) 3.6 CAS缺点 四.原子类 4.1 概述 ...

  6. Java核心知识点学习----线程中如何创建锁和使用锁 Lock,设计一个缓存系统

    理论知识很枯燥,但这些都是基本功,学完可能会忘,但等用的时候,会发觉之前的学习是非常有意义的,学习线程就是这样子的. 1.如何创建锁? Lock lock = new ReentrantLock(); ...

  7. Java:使用synchronized和Lock对象获取对象锁

    在并发环境下,解决共享资源冲突问题时,可以考虑使用锁机制. 1.对象的锁 所有对象都自动含有单一的锁. JVM负责跟踪对象被加锁的次数.如果一个对象被解锁,其计数变为0.在任务(线程)第一次给对象加锁 ...

  8. 《深入浅出 Java Concurrency》—锁紧机构(一)Lock与ReentrantLock

    转会:http://www.blogjava.net/xylz/archive/2010/07/05/325274.html 前面的章节主要谈谈原子操作,至于与原子操作一些相关的问题或者说陷阱就放到最 ...

  9. java 锁 Lock接口详解

    一:java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的) (1)Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是Reen ...

随机推荐

  1. LaTeX常用符号(持续更新)

    参考网址:https://qianwenma.cn/2018/05/17/mathjax-yu-fa-can-kao/# 基本运算 1.乘法$x\times y$ x\times y 2.乘法$x^{ ...

  2. Dotnet core基于ML.net的销售数据预测实践

    ML.net已经进到了1.5版本.作为Microsoft官方的机器学习模型,你不打算用用?   一.前言 ML.net可以让我们很容易地在各种应用场景中将机器学习加入到应用程序中.这是这个框架很重要的 ...

  3. 'ipconfig' 不是内部或外部命令,也不是可运行的程序 或批处理文件

    今天在学习的时候需要找本地ip地址,可是在命令行窗口却显示 百度之后发现原来是环境变量没配置的问题(其实之前是ok的,但应该是anconda安装的时候点了那个一键设置环境变量搞得本地的path里的数据 ...

  4. 三角函数与缓入缓出动画及C#实现(图文讲解)

    日常经常能看到缓入缓出的动画效果,如: 1,带缓入缓出效果的滚动条: 2,带缓入缓出效果的呼吸灯: 像上面这种效果,就是用到了三角函数相关的知识,下面将从头开始一步步去讲解如何实现这种效果. 一.基础 ...

  5. HTML躬行记(1)——SVG

    <svg>是矢量图的根元素,通过xmlns属性声明命名空间,从而告诉用户代理标记名称属于哪个XML方言.在下面的示例中,为<svg>元素声明了宽度和高度(默认以像素为单位),其 ...

  6. docker已运行容器里的时区修改

    ln -sf /usr/share/zoneinfo/Asia/Shanghai    /etc/localtime 或者 cp /usr/share/zoneinfo/Asia/Shanghai  ...

  7. 黎活明8天快速掌握android视频教程--20_采用ContentProvider对外共享数据

    1.内容提供者是让当前的app的数据可以让其他应用访问,其他应该可以通过内容提供者访问当前app的数据库 contentProvider的主要目的是提供一个开发的接口,让其他的应该能够访问当前应用的数 ...

  8. mysql 出现You can't specify target table for update in FROM clause错误的解决方法

    mysql出现You can’t specify target table for update in FROM clause 这个错误的意思是不能在同一个sql语句中,先select同一个表的某些值 ...

  9. IDEA解决SVN频繁弹出登录框

    将HTTP请求改成SVN就可以了,或者请项目经理开启SVN中的HTTP请求

  10. 平时自己常用的git指令

    增删改查 创建标签 $ git tag -a v1.4 -m 'my version 1.4' 用 -a (译注:取 annotated 的首字母)指定标签名字即可 -m 选项则指定了对应的标签说明 ...