Java 显式锁。

一、显式锁

什么是显式锁?

由自己手动获取锁,然后手动释放的锁。

有了 synchronized(内置锁) 为什么还要 Lock(显示锁)?

使用 synchronized 关键字实现了锁功能的,使用 synchronized 关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。

与内置加锁机制不同的是,Lock 提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。

二、Lock的常用api

方法名称 描述
void lock() 获取锁
void lockInterruptibly() throws InterruptedException 可中断的获取锁,和lock()方法的不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程
boolean tryLock() 尝试非阻塞的获取锁,调用该方法后立刻返回,如果能够获取则返回true,否则返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException 超时获取锁,当前线程会在以下三种情况下会返回:
1. 当前线程在超时时间内获得了锁
2.当前线程在超市时间内被中断
3. 超时时间结束,返回false
void unlock(); 释放锁

三、Lock的标准用法

lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
  • 在 finally 块中释放锁,目的是保证在获取到锁之后,最终能够被释放。
  • 不要将获取锁的过程写在 try 块中,因为如果在获取锁(自定义锁的实现)时发生了异常,异常抛出的同时,也会导致锁无故释放。

四、ReentrantLock(可重入锁)

Lock接口常用的实现类是 ReentrantLock。

示例代码:主线程100000次减,子线程10万次加。

public class ReentrantLockTest {

    private Lock lock = new ReentrantLock();
private int count = 0; public int getCount() {
return count;
} private static class ReentrantLockThread extends Thread {
private ReentrantLockTest reentrantLockTest; public ReentrantLockThread(ReentrantLockTest reentrantLockTest) {
this.reentrantLockTest = reentrantLockTest;
} @Override
public void run() {
for (int i = 0; i < 100000; i++) {
reentrantLockTest.incr();
}
System.out.println(Thread.currentThread().getName() + " end, count = " + reentrantLockTest.getCount());
}
} private void incr() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
} private void decr() {
lock.lock();
try {
count--;
} finally {
lock.unlock();
}
} public static void main(String[] args) throws InterruptedException {
ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
new ReentrantLockThread(reentrantLockTest).start(); for (int i = 0; i < 100000; i++) {
// 递减100000
reentrantLockTest.decr();
}
System.out.println(Thread.currentThread().getName() + " count = " + reentrantLockTest.getCount());
}
}

1. 锁的可重入性

简单地讲就是:“同一个线程对于已经获得到的锁,可以多次继续申请到该锁的使用权”。而 synchronized 关键字隐式的支持重进入,比如一个 synchronized 修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁

同样,ReentrantLock 在调用 lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞

2. 公平锁和非公平锁

  • 如果在时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。
  • ReentrantLock 提供了一个构造函数,能够控制锁是否是公平的(缺省为不公平锁)。事实上,公平的锁机制往往没有非公平的效率高。
  • 在激烈竞争的情况下,非公平锁的性能高于公平锁的性能的一个原因是:在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。
  • 假设线程 A 持有一个锁,并且线程 B 请求这个锁,由于这个锁已被线程 A 持有,因此 B 将被挂起。当 A 释放锁时,B 将被唤醒,因此会再次尝试获取锁。与此同时,如果 C 也请求这个锁,那么 C 很可能会在 B 被完全唤醒之前获得、使用以及释放这个锁,这样的情况是一种“双赢”的局面:B 获得锁的时刻并没有推迟,C 更早地获得了锁,完成了自己的任务,然后释放了锁,并且吞吐量也获得了提高。

五、ReentrantReadWriteLock(读写锁)

ReentrantReadWriteLock 是 ReadWriteLock 的实现类。

之前提到锁基本都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

读锁不排斥读锁,但是排斥写锁;写锁即排斥读锁也排斥写锁。

private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock getLock = lock.readLock(); // 读锁
private final Lock setLock = lock.writeLock(); // 写锁

至于上锁、解锁与 ReentrantLock 使用方式一致。

六、Condition

  • 任意一个 Java 对象,都拥有一组监视器方法(定义在 java.lang.Object 上),主要包括 wait()、wait(long timeout)、notify()以及 notifyAll()方法,这些方法与 synchronized 同步关键字配合,可以实现等待/通知模式。
  • Condition 接口也提供了类似 Object 的监视器方法,与 Lock 配合可以实现等待/通知模式。

常用api

方法名称 描述
void await() throws InterruptedException 使当前线程进入等待状态直到被通知(signal)或中断
void signal() 唤醒一个等待的线程
void signalAll() 唤醒所有等待的线程

示例代码,主线程调用方法唤醒两个子线程。

public class ConditionTest {

    private volatile boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition(); private void task1() {
lock.lock();
try {
try {
System.out.println(Thread.currentThread().getName() + " 等待中");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 等待结束");
System.out.println("发送邮件");
} finally {
lock.unlock();
}
} private void task2() {
lock.lock();
try {
try {
System.out.println(Thread.currentThread().getName() + " 等待中");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 等待结束");
System.out.println("发送短信");
} finally {
lock.unlock();
}
} private void updateFlag() {
lock.lock();
try {
this.flag = true;
this.condition.signalAll();
}finally {
lock.unlock();
}
} private static class ConditionThread1 extends Thread {
private ConditionTest conditionTest;
public ConditionThread1(ConditionTest conditionTest) {
this.conditionTest = conditionTest;
} @Override
public void run() {
if (!conditionTest.flag) {
conditionTest.task1();
}
}
} private static class ConditionThread2 extends Thread {
private ConditionTest conditionTest;
public ConditionThread2(ConditionTest conditionTest) {
this.conditionTest = conditionTest;
} @Override
public void run() {
if (!conditionTest.flag) {
conditionTest.task2();
}
}
} public static void main(String[] args) throws InterruptedException {
ConditionTest conditionTest = new ConditionTest();
new ConditionThread1(conditionTest).start();
new ConditionThread2(conditionTest).start();
Thread.sleep(1000);
System.out.println("flag 改变。。。");
conditionTest.updateFlag();
}
}

都读到这里了,来个 点赞、评论、关注、收藏 吧!

文章作者:IT王小二

首发地址:https://www.itwxe.com/posts/4e06845c/

版权声明:文章内容遵循 署名-非商业性使用-禁止演绎 4.0 国际 进行许可,转载请在文章页面明显位置给出作者与原文链接。

Java显式锁的更多相关文章

  1. Java显式锁学习总结之二:使用AbstractQueuedSynchronizer构建同步组件

    Jdk1.5中包含了并发大神Doug Lea写的并发工具包java.util.concurrent,这个工具包中包含了显示锁和其他的实用同步组件.Doug Lea在构建锁和组件的时候,大多是以队列同步 ...

  2. Java显式锁学习总结之六:Condition源码分析

    概述 先来回顾一下java中的等待/通知机制 我们有时会遇到这样的场景:线程A执行到某个点的时候,因为某个条件condition不满足,需要线程A暂停:等到线程B修改了条件condition,使con ...

  3. Java显式锁学习总结之五:ReentrantReadWriteLock源码分析

    概述 我们在介绍AbstractQueuedSynchronizer的时候介绍过,AQS支持独占式同步状态获取/释放.共享式同步状态获取/释放两种模式,对应的典型应用分别是ReentrantLock和 ...

  4. Java显式锁学习总结之四:ReentrantLock源码分析

    概述 ReentrantLock,即重入锁,是一个和synchronized关键字等价的,支持线程重入的互斥锁.只是在synchronized已有功能基础上添加了一些扩展功能. 除了支持可中断获取锁. ...

  5. Java显式锁学习总结之三:AbstractQueuedSynchronizer的实现原理

    概述 上一篇我们讲了AQS的使用,这一篇讲AQS的内部实现原理. 我们前面介绍了,AQS使用一个int变量state表示同步状态,使用一个隐式的FIFO同步队列(隐式队列就是并没有声明这样一个队列,只 ...

  6. Java显式锁学习总结之一:概论

    我们都知道在java中,当多个线程需要并发访问共享资源时需要使用同步,我们经常使用的同步方式就是synchronized关键字,事实上,在jdk1.5之前,只有synchronized一种同步方式.而 ...

  7. Java并发编程之显式锁机制

    我们之前介绍过synchronized关键字实现程序的原子性操作,它的内部也是一种加锁和解锁机制,是一种声明式的编程方式,我们只需要对方法或者代码块进行声明,Java内部帮我们在调用方法之前和结束时加 ...

  8. 深入理解Java内置锁和显式锁

    synchronized and Reentrantlock 多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两 ...

  9. java并发多线程显式锁Condition条件简介分析与监视器 多线程下篇(四)

    Lock接口提供了方法Condition newCondition();用于获取对应锁的条件,可以在这个条件对象上调用监视器方法 可以理解为,原本借助于synchronized关键字以及锁对象,配备了 ...

随机推荐

  1. Spring Security 入门篇

    本文是一个笔记系列,目标是完成一个基于角色的权限访问控制系统(RBAC),有基本的用户.角色.权限管理,重点在Spring Security的各种配置.万丈高楼平地起,接下来,一步一步,由浅入深,希望 ...

  2. java+selenium使用JS、键盘滑动滚动条

    本篇文章介绍如何使用JS和键盘对象对页面进行滑动滚动条-------------主要针对java做自动化测试的同学 一:使用键盘对象操作滚动条 //导包 import org.openqa.selen ...

  3. Spring MVC工作原理及源码解析(一) MVC原理介绍、与IOC容器整合原理

    MVC原理介绍 Spring MVC原理图 上图是Spring MVC工作原理图(图片来自网上搜索),根据上图,我们可以得知Spring MVC的工作流程如下: 1.用户(客户端,即浏览器)发送请求至 ...

  4. 细数 C++ 那些比起 C语言 更爽的特性

    结构体定义 C: typedef struct Vertex { int x, y, z; } Vertex; Vertex v1 = { 0 }; // or struct Vertex { int ...

  5. Markdown使用概述

    Markdown使用概述 序言 作为一名编程学习的爱好者和初学者,由于学习编程的过程中总是存在遗忘以及很难动手写起来的问题,所以在看了许多关于编程学习方法的文章之后,选择使用typora作为我的笔记工 ...

  6. [Java] 类库例题

    例1 字符串操作 定义一个StringBuffer类对象,然后通过append()方法向对象中添加26个小写字母,每次只添加一次,共添加26次,然后按逆序方式输出,并且可以删除前5个字符 面向过程实现 ...

  7. [刷题] 226 Invert Binary Tree

    要求 翻转一棵二叉树 实现 翻转左右子树,交换左右子树的根节点 1 class Solution { 2 public: 3 TreeNode* invertTree(TreeNode* root) ...

  8. python-dict和list常用方法

    1 # *********-list-********* 2 # 创建一个list 3 list1 = [1, 2, '力气,', 'afd'] 4 print(type(list1)) 5 prin ...

  9. Gtkperf介绍

    Gtkperf使用说明一.Gtkperf介绍GtkPerf是一种应用程序设计,测试基于GTK +的性能.问题的关键是建立共同的测试平台,运行预先基于GTK +工具(开放comboboxes ,切换按钮 ...

  10. Linux_部署Ansible

    一.构建Ansible 1.定义清单 清单定义Ansible将要管理的一批主机 这些主机也可以分配到组中,以进行集中管理:组可以包含子组,主机也可以是多个组的成员 清单还可以设置应用到它所定义的主机和 ...