前面我们可以使用synchronized关键字来实现线程之间的同步互斥,lock接口同样也是在JDK1.5中提出,同样是解决线程安全性问题的另一种解决方案,而且它更强大,更灵活本片博客介绍对其展开介绍;

Lock接口有如下几个实现类:

  • ReentrantLock--JDK实现的锁
  • ReentrantReadWritrLock.ReadLock
  • ReentrantReadWriteLock.WriteLock

打个例子

public class Demo01 {
private int i=0; Lock Mylock = new ReentrantLock(); public int add() throws InterruptedException {
try{
Mylock.lock();
i++;
return i;
} finally {
Mylock.unlock();
} }

如上代码 i++ 被 Mylock.lock()和 Mylock.unlock()围住,显示的获取和释放锁,具有同步性...

其中,ReentrantLock一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

Lock 与 Synchronized 相比较,显而易见的就是,Lock需要显示的去获取锁,释放锁,比较繁琐,但是繁琐带来了更大好处,让代码更灵活...Lock是对Synchronized的封装...

比如:

  1. 在控制锁的获取和释放,以及何时何地获取释放
  2. 使用Lock,可以很方便的实现锁的公平性ReentrantLock(boolean fair)
  3. 强大的API
    • 非阻塞获取锁:tryLock()
    • 可中断式获取线程acquireSharedInterruptibly(int arg)
    • 超时获取线程tryAcquireSharedNanos(int arg , long nanosTimeout)

一 . Conditon&Reentrantlock

1. Condition实现正确的通知/等待

  • 注意点,一定要在condition.wait()方法调用之前,使用lock.lock()方法获取对象同步锁,否则抛出异常
    public void waitMethod(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis());
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
lock.unlock();
} } public void signalMethod(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName()+"被唤醒了..."+System.currentTimeMillis());
condition.signal();
}finally {
lock.unlock();
}
} public static void main(String[] args) {
demo1 demo1 = new demo1(); new Thread(()->{
demo1.waitMethod();
}).start(); try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo1.signalMethod();
}
}

运行结果:

Thread-0等待了...1549450097987
main被唤醒了...1549450099988

2 使用多个Condition实现,通知部分线程

接下来就是重头戏了Condition控制通知指定的线程醒来

public void waitMethod(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis());
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
lock.unlock();
} } public void signalMethod(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName()+"被唤醒了..."+System.currentTimeMillis());
condition.signal();
}finally {
lock.unlock();
}
} public static void main(String[] args) {
demo1 demo1 = new demo1(); new Thread(()->{
demo1.waitMethod();
}).start(); try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo1.signalMethod();
}
}

运行结果:

Thread-0等待了...1549450097987
main被唤醒了...1549450099988

2 使用多个Condition实现,通知部分线程

接下来就是重头戏了Condition控制通知指定的线程醒来


/*
* 使用多个Condition, 唤醒指定的线程
* */
public class demo2 {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition(); public void waitA(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis());
try {
conditionA.await();
System.out.println(Thread.currentThread().getName()+"await之后的代码..."+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
lock.unlock();
}
}
public void waitB(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis());
try {
conditionB.await();
System.out.println(Thread.currentThread().getName()+"await之后的代码..."+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
lock.unlock();
}
} public void signalAALL(){
try{
lock.lock();
conditionA.signalAll();
System.out.println(Thread.currentThread().getName()+" 执行唤醒操作. ."+System.currentTimeMillis()); }finally {
lock.unlock();
}
} public void signalBBLL(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName()+" 被唤醒了.."+System.currentTimeMillis());
conditionB.signalAll();
}finally {
lock.unlock();
}
} public static void main(String[] args) {
demo2 demo2 = new demo2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
demo2.waitA();
// demo2.waitB();
}
});
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo2.signalAALL();
System.out.println("mian 线程结束...");
}
}

结果:

pool-1-thread-1等待了...1549459953814
main 执行唤醒操作. .1549459955805
mian 线程结束...
pool-1-thread-1await之后的代码...1549459955805
  • 通过结果可以看到,ReentrantLock对象,可以唤醒指定种类的线程,使用个Condition让线程等待,就用哪个Condition去把它唤醒

公平锁与非公平锁

Lock锁分为公平锁和非公平锁,所谓公平锁,就是表示线程获取锁的顺序,是按照线程加锁的顺序来实现的,也就是FIFO的顺序先进先出的顺序,而非公平锁描述的则是一种锁的随机抢占机制,还可能会导致一些线程根本抢不着锁而被饿死,结果就是不公平了

  • ReentrantLock支持公平锁

ReentrantLock()

创建一个 ReentrantLock 的实例。

构造方法名 简介
ReentrantLock(boolean fair) 创建一个具有给定公平策略的 ReentrantLock。

ReentrantLock几组常用API

第一组:

方法名 作用
int getHoldCount() 查询当前线程保存此锁的个数,(执行lock.lock()的次数),即重入的次数
int getQueueLength() 返回**正在等待获取此锁(调用该方法的锁)的线程的个数,比如有五条线程已经准备就绪了,其中一条线程首先执行lock.await()方法,紧接着调用该方法,获取到的返回值为4,说明有四条线程正在等待此lock
int getWaitQueueLength(Condition condition) 返回正在等待和此锁相关的condition的线程数

第二组:

方法名 作用
boolean hasQueuedThread(Thread thread) 判断当前线程是否正在等到获取到此锁
boolean hasQueuedThreads() 在所有的线程中查询,是否有线程正在等待此所的锁定
boolean hasWaiters(Condition condition) 判断是否有线程正在等待和此condition相关的条件

第三组

方法名 作用
boolean isFair() 判断此锁是不是公平的
boolean isHeldByCurrentThread() 判断当前线程是否拿到了锁
boolean isLocked() 判断此锁是否由任意线程保持

第四组

lock()与lockInterruptibly()

假如出现下面几步,我们这两种加锁方法,会有什么反应?,第一: 线程A启动,使用lock()加锁,第二步: CPU的执行权被线程B抢到,且线程B使用interrupted()方法给线程A打上中断的标记..第三步: 线程A继续执行

  • 使用lock()加锁,假如我们没有使用isInterrupted()判断的话,代码会按照原来的顺序依次全部执行,没有异常,线程AB正常结束
  • 使用lockInterruptibly()加锁,线程A被中断后,会调用lockInterruptibly()报异常,进入catch代码块

tryLock()与Lock()

boolean tryLock()与void lock() 前者是有返回值的,两者都能去获取锁,而tryLock()直意尝试获取锁,有就拿,没有算了,它多了一步判断,它在调用时,会去判断此锁是否被其他线程锁定,如果没有,则获取,并返回ture

if(lock.tryLock()){
//do something 1 ...
}else{
//do something 2...
}

可以看到,使用tryLock(),不至于当前线程被阻塞住

方法名 作用
boolean tryLock(Long timeout,TimeUnit unit) 如果在给定的等待时间内,锁没有被其他线程拿到,并且当前线程也没有被中断,获取锁

await()和awaitUninterrupted()

当线程A await() 线程B给线程A打上中断的标记

  • 1.中断 2,抛出中断异常 3. 进入catch块

当线程A awaitUninterrupted() 线程B给线程A打上中断的标记

  • 1.中断

boolean awaitUtil(Date deadLine);

出现一下几种情况,线程被唤醒

  • 等待的时间结束
  • 在等待.被中断的过程被提前唤醒
  • 被中断

读写锁(排它锁,共享锁)

在一个不需要操作实例变量的方法中,完全可以使用读写锁来提升该方法的代码运行速度

  • 读操作相关的锁是共享锁,写操作相关的锁为排他锁

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

  • 读读共享 lock.readLock().lock();
  • 写写互斥 lock.writeLock().lock();
  • 读写互斥

获取读写锁

    public void read() {
lock.readLock().lock();
System.out.println("获取到了读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
} public void write(){
try {
lock.writeLock().lock();
System.out.println(""+Thread.currentThread().getName()+" " +System.currentTimeMillis());
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.writeLock().unlock();
}
}

创建四条线程测试

获取到了读锁Thread-0  1548851348805
获取到了读锁Thread-1 1548851348805
Thread-2 1548851350806
Thread-3 1548851352806

JDK8新增StampedLock对ReentrantReadWriteLock进行增强

stamped:贴上邮票的; 盖上邮戳的,拿到锁之后会返回给我们一个票据,根据这个Stamp的值判断是在读的时候发生了写,还是在写的时候发生了读操作

解决的问题:

在高并发的情况下,读操作的次数远远大于写操作,,因为读写互斥,写操作可能就会出现饥饿的情况,一直抢占不到cpu的资源

解决方法:

  1. 当然可以使用公平的ReadWriteLock,但是依然有性能问题

  2. StampedLock的乐观锁实现了读写共享提升了!

StampedLock里面有两种锁

乐观锁:

读锁并不会阻塞写锁

 public long tryOptimisticRead() {...}

悲观锁:

读写互斥,和ReentrantReadWriteLock实现相同的功能

API:

独占的获取写锁,若锁被其他线程获取到,则阻塞,注意它是相对于ReentrantReadWriteLock来讲,它是有返回值的,返回值的作用:

  • 释放锁(unlock的需要参数)
  • 进行锁的转换
   /**
* Exclusively acquires the lock, blocking if necessary
* until available.
*
* @return a stamp that can be used to unlock or convert mode
*/
public long writeLock() {
long s, next; // bypass acquireWrite in fully unlocked case only
return ((((s = state) & ABITS) == 0L &&
U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
next : acquireWrite(false, 0L));
}

//一旦写锁可用立即获取,返回值可以用作释放锁或被锁的转换使用, 0表示没有获取到锁
/**
* Exclusively acquires the lock if it is immediately available.
*
* @return a stamp that can be used to unlock or convert mode,
* or zero if the lock is not available
*/
public long tryWriteLock() {
long s, next;
return ((((s = state) & ABITS) == 0L &&
U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
next : 0L);
} //延迟获取锁
/**
* Exclusively acquires the lock if it is available within the
* given time and the current thread has not been interrupted.
* Behavior under timeout and interruption matches that specified
* for method {@link Lock#tryLock(long,TimeUnit)}.
*
* @param time the maximum time to wait for the lock
* @param unit the time unit of the {@code time} argument
* @return a stamp that can be used to unlock or convert mode,
* or zero if the lock is not available
* @throws InterruptedException if the current thread is interrupted
* before acquiring the lock
*/
public long tryWriteLock(long time, TimeUnit unit) ... // 非独占的获取读锁
/**
* Non-exclusively acquires the lock, blocking if necessary
* until available.
*
* @return a stamp that can be used to unlock or convert mode
*/
public long readLock() {
long s = state, next; // bypass acquireRead on common uncontended case
return ((whead == wtail && (s & ABITS) < RFULL &&
U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
next : acquireRead(false, 0L));
} // 一旦锁可用,立即非独占的获取读锁
/**
* Non-exclusively acquires the lock if it is immediately available.
*
* @return a stamp that can be used to unlock or convert mode,
* or zero if the lock is not available
*/
public long tryReadLock() {
for (;;) {
long s, m, next;
if ((m = (s = state) & ABITS) == WBIT)
return 0L;
else if (m < RFULL) {
if (U.compareAndSwapLong(this, STATE, s, next = s + RUNIT))
return next;
}
else if ((next = tryIncReaderOverflow(s)) != 0L)
return next;
}
}

乐观锁!!!它获取到的锁,读写锁非互斥

返回一个标记,这个标记过一会用去 校验, 如果锁是排它锁,返回零


/**
* Returns a stamp that can later be validated, or zero
* if exclusively locked.
*
* @return a stamp, or zero if exclusively locked
*/
public long tryOptimisticRead() {
long s;
return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
}

校验,如果锁还没被任何线程获取,获取被持有当前stamp的线程获取返回true , 如果 stamp为0,返回false

     /**
* Returns true if the lock has not been exclusively acquired
* since issuance of the given stamp. Always returns false if the
* stamp is zero. Always returns true if the stamp represents a
* currently held lock. Invoking this method with a value not
* obtained from {@link #tryOptimisticRead} or a locking method
* for this lock has no defined effect or result.
*
* @param stamp a stamp
* @return {@code true} if the lock has not been exclusively acquired
* since issuance of the given stamp; else false
*/
public boolean validate(long stamp) {
U.loadFence();
return (stamp & SBITS) == (state & SBITS);
}

锁的释放


/**
* If the lock state matches the given stamp, releases the
* exclusive lock.
*
* @param stamp a stamp returned by a write-lock operation
* @throws IllegalMonitorStateException if the stamp does
* not match the current state of this lock
*/
public void unlockWrite(long stamp) {
WNode h;
if (state != stamp || (stamp & WBIT) == 0L)
throw new IllegalMonitorStateException();
state = (stamp += WBIT) == 0L ? ORIGIN : stamp;
if ((h = whead) != null && h.status != 0)
release(h);
} //释放任意匹配成功的锁
/**
* If the lock state matches the given stamp, releases the
* corresponding mode of the lock.
*
* @param stamp a stamp returned by a lock operation
* @throws IllegalMonitorStateException if the stamp does
* not match the current state of this lock
*/
public void unlock(long stamp) {

读,写 锁之间的转换

     public long tryConvertToWriteLock(long stamp) {
..} public long tryConvertToWriteLock(long stamp) {
..}
//释放读锁
public void unlockRead(long stamp) {..} //释放写锁
public void unlockWrite(long stamp) {..}

简单使用后

/*
* StampedLock的简单使用
* */
public class StampedLock01 { private int balance;
StampedLock stamptedLock = new StampedLock(); //悲观读
public void read(){
long s = stamptedLock.readLock();
try{
try {
System.out.println("拿到读锁"+Thread.currentThread().getName()+System.currentTimeMillis());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
stamptedLock.unlockRead(s);
}
} // 乐观锁
public void OptimismRead(){
//获取乐观锁,拿到 标记
long stm = stamptedLock.tryOptimisticRead();
try {
System.out.println("一开始读取到的balance=="+balance); //方便测试 睡一会
Thread.sleep(200);
// 在读的时候,可能会出现现写操作-- 判断
if(!stamptedLock.validate(stm)){ //1 锁空闲可用 2 拥有当前stm的线程获取到锁,返回true 其他返回false
//重新读取
long l = stamptedLock.readLock();
System.out.println("乐观锁中发现了在读时,发现写操作,重新读结果为: "+balance);
// 更新标记, 用于锁的释放
stm=l;
}
}catch (Exception e){
e.printStackTrace();
}finally {
//释放锁
stamptedLock.unlockRead(stm); }
} // 带条件的读写锁
public void MyConditionReadWriteLock(int v){
//判断balance是否符合更新的条件
long stm = stamptedLock.readLock();
try{ // 为什么不会if 而用while
while(stm>0){
//转换成写锁;
long s1 = stamptedLock.tryConvertToWriteLock(stm);
if (s1!=0){// 成功转换
balance+=v;
System.out.println("进行写操作: balance=="+balance+99);
stm=s1;
break;
}else{ //没有转换成功
//释放读锁
stamptedLock.unlockRead(stm);
// 获取写
long s2 = stamptedLock.writeLock();
stm=s2;
System.out.println("手动获取写锁...");
}
}
}finally {
stamptedLock.unlock(stm);
} } //独占的写
public void write(int v){
long stamp = stamptedLock.writeLock();
try{
balance+=v;
System.out.println("进行写的操作...结果: "+balance+" "+Thread.currentThread().getName()+" "+"write中的标记为:"+stamp);
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
finally {
stamptedLock.unlockWrite(stamp);
}
} public static void main(String[] args) { StampedLock01 stampedLock01 = new StampedLock01(); //测试普通的read // 测试成功 异步执行 //测试独占的写 成功
/* new Thread(()->{
// 乐观读
stampedLock01.OptimismRead();
}).start(); new Thread(()->{
stampedLock01.write(3);
}).start();
*/
new Thread(()->{
stampedLock01.MyConditionReadWriteLock(1);
}).start();
} }

最后提一下 > 如何选择大名鼎鼎的AQS最有名的实现类ReentrantLocksynchronized这个JVM提供的锁,一开始synchronized是一个笨重的重量级锁,但是jdk1.5之后,进行了偏向锁,轻量级锁的优化,使它的性能和ReentrantLock擦不多了,于是没有特殊的要求,官方推荐使用synchronized

什么情况下使用ReentrantLock呢?

  • 使用它特有的公平锁
  • 使用它的Condition类,分组唤醒指定的线程
  • 提供了能够中断正在等待锁的线程的机制,lock.lockInterrupted()

多线程八 Lock的更多相关文章

  1. c#初学-多线程中lock用法的经典实例

    本文转载自:http://www.cnblogs.com/promise-7/articles/2354077.html 一.Lock定义     lock 关键字可以用来确保代码块完成运行,而不会被 ...

  2. python多线程threading.Lock锁用法实例

    本文实例讲述了python多线程threading.Lock锁的用法实例,分享给大家供大家参考.具体分析如下: python的锁可以独立提取出来 mutex = threading.Lock() #锁 ...

  3. 多线程中lock用法的经典实例

    多线程中lock用法的经典实例 一.Lock定义     lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断.它可以把一段代码定义为互斥段(critical section),互斥段在一 ...

  4. c/c++ 多线程 std::lock

    多线程 std::lock 当要同时操作2个对象时,就需要同时锁定这2个对象,而不是先锁定一个,然后再锁定另一个.同时锁定多个对象的方法:std::lock(对象1.锁,对象2.锁...) 额外说明: ...

  5. c#多线程中Lock()关键字的用法小结

    本篇文章主要是对c#多线程中Lock()关键字的用法进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助     本文介绍C# lock关键字,C#提供了一个关键字lock,它可以把一段 ...

  6. c#中多线程同步Lock(锁)的研究以及跨线程UI的操作

    本文只针对C#中,多线程同步所用到的锁(lock)作为研究对象.由于想更直观的显示结果,所以,在做demo的时候,就把多线程通过事件操作UI的代码也写了出来,留作备忘和分享吧. 其实多线程的同步,使用 ...

  7. C++开发过程多线程同步lock的实现

    在程序开发过程经常使用到多线程,而多线程始终与锁存在紧密地联系,以下详细的介绍如何在C++程序开发过程中自定义锁的几种方法. 1. 下面给出一段代码展现如何通过Mutex实现锁的功能(window p ...

  8. Java多线程(五) Lock接口,ReentranctLock,ReentrantReadWriteLock

    在JDK5里面,提供了一个Lock接口.该接口通过底层框架的形式为设计更面向对象.可更加细粒度控制线程代码.更灵活控制线程通信提供了基础.实现Lock接口且使用得比较多的是可重入锁(Reentrant ...

  9. Java多线程的~~~Lock接口和ReentrantLock使用

    在多线程开发.除了synchronized这个keyword外,我们还通过Lock接口来实现这样的效果.由Lock接口来实现 这样的多线程加锁效果的优点是非常的灵活,我们不在须要对整个函数加锁,并且能 ...

随机推荐

  1. 深入解析Java反射

    因为本人最近正筹备Samsara框架的开发,而其中的IOC部分非常依靠反射,因此趁这个机会来总结一下关于Java反射的一些知识.本篇为基本篇,基于JDK 1.8. 一.回顾:什么是反射? 反射(Ref ...

  2. ssm项目集成

    ssm项目集成 说明:ssm指的是 spring + springMvc+ mybatis,是目前开发比较流行的集成方式,可以较好的实现前后端分离 spring与mybatis的集成,是通过配置文件a ...

  3. c#截取后台窗口的图片

    c#截取后台窗口的图片,自测可用,据说性能很一般,用用吧 struct RECT { public int Left; // x position of upper-left corner publi ...

  4. 如何为.NETCore安装汉化包智能感知

    引言 具体不记得是在群里还是什么地方有人问过,.NETCore有没有汉化包,答案是有,目前微软已经为我们提供了.NETCore多种语言的语言包.下面看看如何安装与使用吧. 在哪下载? 在微软官方下载 ...

  5. JavaSE-知识点总结

    Java名词 变量.运算符.类.接口.枚举.参数.注解.异常.包装类.多线程.集合.IO流.网络编程.反射.Lambda.API 源文件:.java文件,存储Java源代码的文件 字节码文件:.cla ...

  6. 【转载】[C++ STL] deque使用详解

    转载自 https://www.cnblogs.com/linuxAndMcu/p/10260124.html 一.概述 deque(双端队列)是由一段一段的定量连续空间构成,可以向两端发展,因此不论 ...

  7. Blockchain 基本知识

    本文是前奏,本来要介绍Azure上的Azure Blockchain Service,发现,需要从什么是区块链开始讲起... 什么是区块链?我们从比特币说起, 2008年11月,中本聪提出了比特币白皮 ...

  8. cuckoo沙箱技术分析全景图

    从事信息安全技术行业的小伙伴们都知道沙箱技术(有些也称沙盒),用来判断一个程序或者文件是否是恶意的病毒.木马.漏洞攻击exp或其他恶意软件.其原理简单来说就是提供了一个虚拟的环境,把分析目标放到这个虚 ...

  9. 一起学Spring之Web基础篇

    概述 在日常的开发中Web项目集成Spring框架,已经越来越重要,而Spring框架已经成为web开发的主流框架之一.本文主要讲解Java开发Web项目集成Spring框架的简单使用,以及使用Spr ...

  10. 一起学Spring之注解和Schema方式实现AOP

    概述 在上一篇,我们了解了通过实现接口和XML配置的方式来实现AOP,在实现注解方式AOP之前,先了解一下AspectJ.AspectJ是一个面向切面的框架,它扩展了Java语言,定义了AOP语法,能 ...