本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。

并发编程系列博客传送门


Lock接口简介

在JUC包下面有一个java.util.concurrent.locks包,这个包提供了一系列基础的锁工具,对传统的synchronizd、wait和notify等同步机制进行补充和增强。下面先来介绍下这个Lock接口。

Lock接口可以视为synchronized的增强版,提供了更灵活的功能。相对于synchronized,Lock接口还提供了限时锁等待、锁中断和锁尝试等功能。该接口的定义如下


public interface Lock {
// 尝试去获得锁
// 如果锁不可用,当前线程会变得不可用,直到获得锁为止。(中途会忽略中断)
void lock(); // 尝试去获取锁,如果锁获取不到,线程将不可用
// 直到获取锁,或者被其他线程中断
// 线程在获取锁操作中,被其他线程中断,则会抛出InterruptedException异常,并且将中断标识清除。
void lockInterruptibly() throws InterruptedException; // 锁空闲时返回true,锁不空闲是返回false
// 该方法不会引起当前线程阻塞
boolean tryLock(); // 在unit时间内成功获取锁,返回true
// 在unit时间内未成功获取锁,返回false
// 如果当前线程在获取锁操作中,被其他线程中断,则会抛出InterruptedException异常,并且将中断标识清除。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 释放锁
void unlock(); // 获取一个绑定到当前Lock对象的Condition对象
// 获取Condition对象的前提是当前线程持有Lock对象
Condition newCondition();
}

关于上面的lock()和lockInterruptibly()方法,有如下区别:

lock()方法类似于使用synchronized关键字加锁,如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态。

lockInterruptibly()方法顾名思义,就是如果锁不可用,那么当前正在等待的线程是可以被中断的,这比synchronized关键字更加灵活。

Lock接口的经典用法

Lock lock = new ReentrantLock();
//尝试获取锁,如果当前该锁没有被其他线程持有,则当前线程获取该锁并返回true,否则返回false。
//该方法不会引起当前线程阻塞
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}

或者是

lock.lock()
try {
// manipulate protected state
} finally {
lock.unlock();
}

这边不要将获取锁的过程写在try块中,因为如果在获取锁(自定义锁的实现)时发生了异常,异常抛出的同时,也会导致锁无故释放。

ReentrantLock

ReentrantLock类是一个可重入的独占锁,除了具有和synchronized一样的功能外,还具有限时锁等待、锁中断和锁尝试等功能。

ReentrantLock底层是通过继承AQS来实现独占锁功能的。

公平锁和非公平锁

关于ReentrantLock,有两个很重要的概念需要学习:公平锁和非公平锁

查看ReentrantLock的源代码,我们会看到两个构造函数,分为对应构造公平锁和非公平锁。

//默认构造非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//true构造公平锁,false构造非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

公平锁:是指线程在抢占锁失败后会进入一个等待队列,先进入队列的线程会先获得锁。公平性体现在先来先得。

非公平锁:是指线程抢占锁失败后会进入一个等待队列,但是这些等待线程谁能先获得锁不是按照先来先得的规则,而是随机的。不公平性体现在后来的线程可能先得到锁。

如果有很多线程竞争一把公平锁,系统的总体吞吐量(即速度很慢,常常极其慢)比较低,因为此时在线程调度上面的开销比较大。

原因是采用公平策略时,当一个线程释放锁时,需要先将等待队列中的线程唤醒。这个唤醒的调度过程是比较耗费时间的。如果使用非公平锁的话,当一个线程释放锁之后,可用的线程能立马获得锁,效率较高。

ReentrantLock代码实现

1. 非公平锁代码

static final class NonfairSync extends Sync {

    private static final long serialVersionUID = 7316153563782823691L;

    /**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
//如果没有线程占据锁,则占据锁,也就是将state从0设置为1
//这种抢占方式不要排队,有人释放了锁,你可以直接插到第一位
//去抢,只要你能抢到
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//否则尝试抢占锁
acquire(1);
} protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}

通过之前对AQS的介绍,我们知道抢占锁的时候会调用 tryAcquire 方法。非公平锁的这个方法直接调用了父类中的nonfairTryAcquire


final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//锁已经被释放,则直接占据锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//否则判断锁是不是之前被自己占用过,并设置重入次数
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

1. 公平锁代码


static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L; final void lock() {
acquire(1);
} protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//没人在队列中排队,并且锁已经被释放才能抢占到锁,否则去队列中排队
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//设置重入次数
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}

同步锁——ReentrantLock的更多相关文章

  1. Spring @Transactional注解和ReentrantLock同步锁同时使用不能同步的问题

    结论:如果在service层的方法上同时使用事务和同步锁无法保证数据同步. 1 @Service 2 public class ServiceImpl{ 3 4 private static Lock ...

  2. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

  3. Lock同步锁--线程同步

    Lock-同步锁 Lock是java5提供的一个强大的线程同步机制--通过显示定义同步锁对象来实现同步.Lock可以显示的加锁.解锁.每次只能有一个线程对lock对象加锁. Lock有ReadLock ...

  4. synchronized关键字,Lock接口以及可重入锁ReentrantLock

    多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...

  5. Java中的显示锁 ReentrantLock 和 ReentrantReadWriteLock

    在Java1.5中引入了两种显示锁,分别是可重入锁ReentrantLock和可重入读写锁ReentrantReadWriteLock.它们分别实现接口Lock和ReadWriteLock.(注意:s ...

  6. JUC回顾之-可重入的互斥锁ReentrantLock

    1.什么是可重锁ReentrantLock? 就是支持重新进入的锁,表示该锁能够支持一个线程对资源的重复加锁. 2.ReentrantLock分为公平锁和非公平锁:区别是在于获取锁的机制上是否公平. ...

  7. java 同步锁方法

    方法一:动态同步锁 class Demo_thread implements Runnable{ public static int sum = 0; public synchronized void ...

  8. 线程同步 synchronized 同步代码块 同步方法 同步锁

    一 同步代码块 1.为了解决并发操作可能造成的异常,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块.其语法如下: synchronized(obj){ // ...

  9. Java并发编程:同步锁、读写锁

    之前我们说过线程安全问题可以用锁机制来解决,即线程必要要先获得锁,之后才能进行其他操作.其实在 Java 的 API 中有这样一些锁类可以提供给我们使用,与其他对象作为锁相比,它们具有更强大的功能. ...

随机推荐

  1. FileUpload的控件上传excel

    在一个使用FileUpload的控件上传excel,读取excel的数据 因为上传的路径一直被限定在C:\Program\IIS\Express 一直限制这个文件下, 想要解决这个问题. 在谷歌浏览器 ...

  2. C# html生成图片保存下载

    最近有个需求,需要把内容生成图片,我找到一些资料可以将html页面生成图片并保存下载 下面是简单的实现 1.html页面 @{ Layout = null; } <!DOCTYPE html&g ...

  3. python小功能记录

    本博客会不断完善,记录python小功能. 1. 合并两个字典 # in Python 3.5+ >>> x = {'a': 1, 'b': 2} >>> y = ...

  4. Unity事件系统EventSystem简析

    相关组件和类 EventSystem 1.负责InputModule的切换(因为现在游戏大部分都只有一个StanaloneInputModule,所以切换这部分可以先不考虑). 2.负责InputMo ...

  5. 关于python列表的一些基础知识。

    因学校实验室要求,自学了python,一开始看书觉得太简单了,也没有多动手去尝试,直到看完了前八章突然想动手试试的时候,人傻了,深刻体会到了好记性不如烂笔头的道理,故整理一些python列表的操作. ...

  6. VSCode前端 插件

    https://www.cnblogs.com/karthuslorin/p/8577224.html

  7. [bzoj2152] [洛谷P2634] 聪聪可可

    Description 聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃.两个人都想玩儿电脑(可是他们家只有一台电脑)--遇到这种问题,一般情况下石头剪刀布就好 ...

  8. [bzoj4417] [洛谷P3990] [Shoi2013] 超级跳马

    Description 现有一个n行m列的棋盘,一只马欲从棋盘的左上角跳到右下角.每一步它向右跳奇数列,且跳到本行或相邻行.跳越期间,马不能离开棋盘.例如,当n = 3, m = 10时,下图是一种可 ...

  9. 10、python函数

    前言:本文主要介绍python函数的定义和调用.函数的参数.函数的作用域.内置函数. 一.函数的定义和调用 1.函数的作用 对特定的一些功能进行封装,提高代码的重用率,进而提升开发的效率,格式: de ...

  10. 几种常见的排序方法总结(Python)

    几种常见的排序算法总结(Python) 排序算法:是一种能将一串数据依照特定顺序进行排序的一种算法. 稳定性:稳定排序算法会让原本有相等键值的记录维持相对次序.也就是如果一个排序算法是稳定的,当有两个 ...