一、引言

  线程并发的过程中,肯定会设计到一个变量共享的概念,那么我们在多线程运行过程中,怎么保证每个先拿获取的变量信息都是最新且有序的呢?这一篇我们来专门学习一下Lock锁。

  我们先来了解几个概念:

乐观锁与悲观锁

悲观锁:

  假定会发生并发冲突,即共享资源会被某个线程更改。所以当某个线程获取共享资源时,会阻止别的线程获取共享资源。也称独占锁或者互斥锁,例如java中的synchronized同步锁。

乐观锁:

  假设不会发生并发冲突,只有在最后更新共享资源的时候会判断一下在此期间有没有别的线程修改了这个共享资源。如果发生冲突就重试,直到没有冲突,更新成功。CAS就是一种乐观锁实现方式。

PS:CAS相关知识戳这里~

公平锁与非公平锁

  • 公平锁的实现就是谁等待时间最长,谁就先获取锁
  • 非公平锁就是随机获取的过程,谁运气好,cpu时间片轮询到哪个线程,哪个线程就能获取锁

可重入锁与不可重入锁

不可重入锁

  若当前线程执行中已经获取了锁,如果再次获取该锁时,就会获取不到被阻塞。

可重入锁

  每一个锁关联一个线程持有者计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

二、Condition

  在使用Lock之前,我们使用的最多的同步方式应该是synchronized关键字来实现同步方式了。配合Object的wait()notify()系列方法可以实现线程的等待/通知模式

  PS:Condition的实质是通过控制线程的等待和唤醒来达到控制指定线程的功能。

  特点

  • 依赖于Lock对象,调用Lock对象的newCondition()对象创建而来
  • 可以实现等待/通知形式的线程交互模式
  • 可以有选择性的进行线程通知,唤醒指定线程

基本方法:

public interface Condition {
void await() throws InterruptedException;
boolean await(long var1, TimeUnit var3) throws InterruptedException;
long awaitNanos(long var1) throws InterruptedException;
void awaitUninterruptibly();
boolean awaitUntil(Date var1) throws InterruptedException;
void signal();
void signalAll();
}
  • await() :造成当前线程在接到信号或被中断之前一直处于等待状态。
  • await(long time, TimeUnit unit) :造成当前线程在接到信号、被中断到达指定等待时间之前一直处于等待状态。
  • awaitNanos(long nanosTimeout) :造成当前线程在接到信号、被中断到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值 = nanosTimeout - 消耗时间,如果返回值 <= 0 ,则可以认定它已经超时了。
  • awaitUninterruptibly() :造成当前线程在接到信号之前一直处于等待状态。【注意:该方法对中断不敏感】。
  • awaitUntil(Date deadline) :造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回返回false。
  • signal() :唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁。
  • signalAll() :唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁。

使用举例:

/**
* Condition使用范例
*
* @author 有梦想的肥宅
*/
public class ConditionTest { //1、创建一个Lock对象,Condition的使用需要依赖Lock对象
public Lock lock = new ReentrantLock();
//2、使用Lock对象的newCondition()方法来生成Condition对象
public Condition condition = lock.newCondition(); //3、main方法测试Condition的作用
public static void main(String[] args) {
ConditionTest conditionTest = new ConditionTest();
//3.1、构造一个容量为2的固定线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
//3.2、执行conditionWait()方法使线程进入“等待”状态
executorService.execute(new Runnable() {
@Override
public void run() {
conditionTest.conditionWait();
}
});
//3.3、conditionSignal()方法“唤醒”线程
executorService.execute(new Runnable() {
@Override
public void run() {
conditionTest.conditionSignal();
}
});
} /**
* 线程等待
*/
public void conditionWait() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "拿到锁了");
System.out.println(Thread.currentThread().getName() + "等待信号");
condition.await();//线程进入等待状态,不进入finally语句块进行锁的释放,要等待被唤醒
System.out.println(Thread.currentThread().getName() + "拿到信号");
} catch (Exception e) { } finally {
lock.unlock();
}
} /**
* 线程唤醒
*/
public void conditionSignal() {
lock.lock();
try {
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "拿到锁了");
condition.signal();//唤醒线程
System.out.println(Thread.currentThread().getName() + "发出信号");
} catch (Exception e) { } finally {
lock.unlock();
}
} }

三、ReentrantLock可重入锁

  ReentrantLock:是一个可重入锁,且它可以设置自身非公平锁或者是公平锁

  常用方法:

  • ReentrantLock() : 创建一个ReentrantLock实例【默认非公平锁】
  • lock() : 获得锁
  • unlock() : 释放锁
/**
* ReentrantLock测试类
*
* @author 有梦想的肥宅
*/
public class ReentrantLockTest {
//全局对象lock【构造参数设置为true表示为公平锁,false或为空则默认是非公平锁】
private static Lock lock = new ReentrantLock(true); //线程方法
public static void test() {
for (int i = 0; i < 2; i++) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获取了锁");
TimeUnit.MILLISECONDS.sleep(1000);//等待1秒,为了更直观地观察公平锁的机制
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
} //运行方法
public static void main(String[] args) {
System.out.println("=====公平锁实例=====");
//启动一个名叫“线程A”的线程
new Thread("线程A") {
@Override
public void run() {
test();
}
}.start();
//启动一个名叫“线程B”的线程
new Thread("线程B") {
@Override
public void run() {
test();
}
}.start();
}
}

ReentrantLock与synchronized的比较

相似点

  它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,等到释放掉锁或者唤醒后才能继续获得锁。

区别

  1️⃣对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成

  2️⃣便利性:Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。

  3️⃣锁的细粒度和灵活度:ReenTrantLock优于Synchronized【可以指定在哪加锁和解锁】

四、ReentrantReadWriteLock可重入读写锁

  定义:ReentrantReadWriteLock是一种可重入读写锁,内部有两把锁来实现读和写的锁功能,在ReentrantLock的基础上优化了性能,但是使用起来需要更加谨慎。

  性质:

可重入

  如果你了解过synchronized关键字,一定知道他的可重入性,可重入就是同一个线程可以重复加锁,每次加锁的时候count值加1,每次释放锁的时候count减1,直到count为0,其他的线程才可以再次获取。

读写分离

  我们知道,对于一个数据,不管是几个线程同时读都不会出现任何问题,但是写就不一样了,几个线程对同一个数据进行更改就可能会出现数据不一致的问题,因此想出了一个方法就是对数据加锁,这时候出现了一个问题:线程写数据的时候加锁是为了确保数据的准确性,但是线程读数据的时候再加锁就会大大降低效率,这时候怎么办呢?那就对写数据和读数据分开,加上两把不同的锁,不仅保证了正确性,还能提高效率。

锁可以降级

  线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级的特性。

锁不可升级

  线程获取读锁是不能直接升级为写入锁的。需要释放所有读取锁,才可获取写锁,我们理解了上面的概念之后,接下来我们看看如何去使用。

  使用示例:

/**
* ReentrantReadWriteLock测试类【可重入读写锁】
*
* @author 有梦想的肥宅
*/
public class ReentrantReadWriteLockTest {
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true);//全局可重入读写锁对象
private final Lock readLock = reentrantReadWriteLock.readLock();//读锁
private final Lock writeLock = reentrantReadWriteLock.writeLock();//写锁
private final List<String> data = new ArrayList<>();//模拟被操作的数据 /**
* 写数据的方法
* @Description 使用writeLock获取一把写锁,然后内部List写入数据,最后在finally中释放写锁。
*/
public void write() {
try {
//1、加上写锁
writeLock.lock();
//2、操作公共数据
data.add("写数据");
System.out.println("当前线程" + Thread.currentThread().getName() + "正在写数据");
//3、线程等待3秒
Thread.sleep(3000);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
//4、释放写锁
writeLock.unlock();
}
} /**
* 读数据的方法
* @Description 使用readLock获取一把读锁,然后内部List读取数据,最后再finally中释放读锁。
*/
public void read() {
try {
//1、加上写锁
writeLock.lock();
//2、读取公共数据
for (String str : data) {
System.out.println("当前线程" + Thread.currentThread().getName() + "正在读数据");
}
//3、线程等待3秒
Thread.sleep(3000);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
//4、释放读锁
readLock.unlock();
}
}
}

参考资料:

并发编程(八)Lock锁的更多相关文章

  1. [转载] java并发编程:Lock(线程锁)

    作者:海子 原文链接: http://www.cnblogs.com/dolphin0520/p/3923167.html 出处:http://www.cnblogs.com/dolphin0520/ ...

  2. 并发编程 17—— Lock

    Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...

  3. 【多线程】Java并发编程:Lock(转载)

    原文链接:http://www.cnblogs.com/dolphin0520/p/3923167.html Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized ...

  4. Java并发编程:Lock(转)

    本文转自:http://www.cnblogs.com/dolphin0520/p/3923167.html Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized ...

  5. 5、Java并发编程:Lock

    Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.l ...

  6. Java并发编程:Concurrent锁机制解析

    Java并发编程:Concurrent锁机制解析 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: # ...

  7. 【java并发编程】Lock & Condition 协调同步生产消费

    一.协调生产/消费的需求 本文内容主要想向大家介绍一下Lock结合Condition的使用方法,为了更好的理解Lock锁与Condition锁信号,我们来手写一个ArrayBlockingQueue. ...

  8. python 并发编程 多进程 互斥锁 目录

    python 并发编程 多进程 互斥锁 模拟抢票 互斥锁与join区别

  9. 转: 【Java并发编程】之二十:并发新特性—Lock锁和条件变量(含代码)

    简单使用Lock锁 Java5中引入了新的锁机制--Java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作.Lock接 ...

  10. Python之路(第三十八篇) 并发编程:进程同步锁/互斥锁、信号量、事件、队列、生产者消费者模型

    一.进程锁(同步锁/互斥锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 例 ...

随机推荐

  1. Tmux安装和使用

    1.What's tmux tmux 是一个终端复用器: 可以激活多个终端或窗口, 在每个终端都可以单独访问,每一个终端都可以访问,运行和控制各自的程序.tmux类似于screen,可以关闭窗口将程序 ...

  2. 程序员深夜惨遭老婆鄙视,原因竟是CAS原理太简单?| 每一张图都力求精美

    悟空 种树比较好的时间是十年前,其次是现在. 自主开发了Java学习平台.PMP刷题小程序.目前主修Java.多线程.SpringBoot.SpringCloud.k8s. 本公众号不限于分享技术,也 ...

  3. ms-data(转载)

    转载:https://www.cnblogs.com/zll-52011/p/10960905.html 1.从美国矿物数据库下载矿物CIF(有晶格) 2.晶胞CIF导入MS 3.选择display ...

  4. 七夕节来啦!AI一键生成情诗,去发给你的女朋友吧!

    [摘要] Hello大家好,今天就是七夕节了,为了增进和女朋友之间的情感,我写了一个自动生成情诗的AI: 大家可以在ModelArts尝试复现模型,然后快去发给你们的女朋友吧- 大家好,我是b站up主 ...

  5. 牛客网数据库SQL实战解析(31-40题)

    牛客网SQL刷题地址: https://www.nowcoder.com/ta/sql?page=0 牛客网数据库SQL实战解析(01-10题): https://blog.csdn.net/u010 ...

  6. C言语--冒泡排序

    /* 冒泡排序,从小到大 */ include<stdio.h> int main(void) { int i; int t; int p; int val; int a[6]; for( ...

  7. 大牛浅谈Web测试基于实际测试的功能测试点总结

    今天跟大家讲解的是web测试在实际测试的功能测试点的一些小总结,希望对你们有帮助,有说的不好的地方,还请多多指教! 一.页面链接检查:测试每一个链接是否都有对应的页面,并且页面之前可以正确切换.   ...

  8. 力扣Leetcode 461. 汉明距离

    给你一个数组 arr ,请你将每个元素用它右边最大的元素替换,如果是最后一个元素,用 -1 替换. 完成所有替换操作后,请你返回这个数组. 示例: 输入:arr = [17,18,5,4,6,1] 输 ...

  9. el-dialog“闪动”解决办法

    问题描述:el-dialog关闭的时候总是出现两次弹窗 解决思路:既然是el-dialog产生的那就直接杀掉el-dialog 代码实践:在el-dialog上添加上一个v-if,值就是用闭窗的值,促 ...

  10. day42:HTML标签和CSS选择器

    目录 1.HTML 1.1 文档结构 1.2 head标签 1.3 body标签 1.3.1 h1-h6标签 1.3.2.br标签:换行 1.3.3.hr标签:一行横线 1.3.4 a标签:超链接标签 ...