首页 > 编程语言 >Java并发编程一ReentrantReadWriteLock初使用

Java并发编程一ReentrantReadWriteLock初使用

时间:2022-11-09 18:36:45浏览次数:40  
标签:Java Thread ReentrantReadWriteLock 编程 sync 读锁 线程 public


推荐:​​Java并发编程汇总​​

Java并发编程一ReentrantReadWriteLock初使用

​ReentrantReadWriteLock​​是一种读写锁,从类名也可以看出来。

​ReentrantReadWriteLock​​​类有两个属性​​ReentrantReadWriteLock.ReadLock readerLock​​​(代表读锁)、​​ReentrantReadWriteLock.WriteLock writerLock​​(代表写锁)。

/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;

​ReadLock​​​类和​​WriteLock​​​类都是​​ReentrantReadWriteLock​​​类的内部类,它们都实现了​​Lock​​接口。

public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;

protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}

public void lock() {
sync.acquireShared(1);
}

public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

public boolean tryLock() {
return sync.tryReadLock();
}

public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

public void unlock() {
sync.releaseShared(1);
}

public Condition newCondition() {
throw new UnsupportedOperationException();
}

public String toString() {
int r = sync.getReadLockCount();
return super.toString() +
"[Read locks = " + r + "]";
}
}
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;

protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}

public void lock() {
sync.acquire(1);
}

public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}

public boolean tryLock( ) {
return sync.tryWriteLock();
}

public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

public void unlock() {
sync.release(1);
}

public Condition newCondition() {
return sync.newCondition();
}

public String toString() {
Thread o = sync.getOwner();
return super.toString() + ((o == null) ?
"[Unlocked]" :
"[Locked by thread " + o.getName() + "]");
}

public int getHoldCount() {
return sync.getWriteHoldCount();
}
}

我们来演示一下怎么使用​​ReentrantReadWriteLock​​。

测试代码:

package lock.readwrite;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CinemaReadWrite {

private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

private static void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
}
}

private static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写入");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
}
}

public static void main(String[] args) {
new Thread(()->read(),"Thread1").start();
new Thread(()->read(),"Thread2").start();
new Thread(()->write(),"Thread3").start();
new Thread(()->write(),"Thread4").start();
}
}

输出:

Thread1得到了读锁,正在读取
Thread2得到了读锁,正在读取
Thread2释放读锁
Thread1释放读锁
Thread3得到了写锁,正在写入
Thread3释放写锁
Thread4得到了写锁,正在写入
Thread4释放写锁

很明显可以看出,读锁是可以被多个线程同时持有的,而写锁却不行,这也很正常,读数据并不会修改数据,所以多个线程一起持有读锁也是线程安全的,而写数据就不一样了。

规则如下:

  • 线程持有读锁,其他线程想要获取读锁是可行的。
  • 线程持有读锁,其他线程想要获取写锁是不可行的。
  • 线程持有写锁,其他线程想要获取读锁是不可行的。
  • 线程持有写锁,其他线程想要获取写锁是不可行的。

读写锁的方法就不演示了,看下面这篇博客即可。

​​Java并发编程一Lock和ReentrantLock初使用​​

fair属性

设置​​fair​​​属性为​​true​​​,则表明为公平锁,设置为​​false​​,则表明为非公平锁。

首先来看看设置​​fair​​​属性为​​true​​的情况。

/**
* Creates a new {@code ReentrantReadWriteLock} with
* the given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}

​fair​​​属性为​​true​​​,​​sync​​​会被赋值为​​new FairSync()​​。

/**
* Fair version of Sync
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}

看看​​hasQueuedPredecessors()​​的源码。

/**
* Queries whether any threads have been waiting to acquire longer
* than the current thread.
*/
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

从代码与注释中,可以很明显看出,如果有其他线程排在当前线程前面,当前线程就要等待前面的线程先获取锁,读写锁都是如此,这种获取锁的顺序,可见是公平的,先来先得。

再来看看设置​​fair​​​属性为​​false​​的情况。

​fair​​​属性为​​false​​​,​​sync​​​会被赋值为​​new NonfairSync()​​。

/**
* Nonfair version of Sync
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}

​writerShouldBlock()​​​一直会返回为​​false​​,可见想要获取写锁的当前线程不需要等待前面的线程先获取锁,它是可以去抢锁的。

/**
* Returns {@code true} if the apparent first queued thread, if one
* exists, is waiting in exclusive mode. If this method returns
* {@code true}, and the current thread is attempting to acquire in
* shared mode (that is, this method is invoked from {@link
* #tryAcquireShared}) then it is guaranteed that the current thread
* is not the first queued thread. Used only as a heuristic in
* ReentrantReadWriteLock.
*/
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}

​isShared()​​是用来判断该结点是不是在等待共享锁,也就是读锁,所以从上面的代码我们可以知道,如果第一个结点是想要获取写锁的,那么想要获取读锁的当前线程是不能抢的,否则是可以抢的。

锁降级

​写锁是可以降级为读锁的,而读锁是不可以升级为写锁的​​​(认为写锁比读锁级别更高)。
测试代码:

package lock.readwrite;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Upgrading {

private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(
false);
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

private static void readUpgrading() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
Thread.sleep(1000);
System.out.println("升级会带来阻塞");
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "获取到了写锁,升级成功");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
}
}

private static void writeDowngrading() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写入");
Thread.sleep(1000);
readLock.lock();
System.out.println("在不释放写锁的情况下,直接获取读锁,成功降级");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
}
}

public static void main(String[] args) throws InterruptedException {
System.out.println("演示降级是可行的");
Thread thread1 = new Thread(() -> writeDowngrading(), "Thread1");
thread1.start();
thread1.join();
System.out.println("------------------");
System.out.println("演示升级是不行的");
Thread thread2 = new Thread(() -> readUpgrading(), "Thread2");
thread2.start();
}
}

输出:

演示降级是可行的
Thread1得到了写锁,正在写入
在不释放写锁的情况下,直接获取读锁,成功降级
Thread1释放读锁
Thread1释放写锁
------------------
演示升级是不行的
Thread2得到了读锁,正在读取
升级会带来阻塞

Java并发编程一ReentrantReadWriteLock初使用_java


线程在锁升级过程中被一直阻塞着,这也很明显验证了​​写锁是可以降级为读锁的,而读锁是不可以升级为写锁的​​。为什么不可以呢?这样设计是有原因的,因为锁升级容易引起死锁。

假如,现在线程​​A​​​与线程​​B​​​都持有了读锁,这是允许的,现在它们都想要进行锁升级,就是升级为写锁,所以线程​​A​​​要等待线程​​B​​​释放掉读锁才能去升级成写锁,而线程​​B​​​要等待线程​​A​​释放掉读锁才能去升级成写锁,这样就造成了死锁。

而锁降级是安全的,因为写锁只能由一个线程持有,该线程进行锁降级是很自由的,没有其他线程来干扰。


标签:Java,Thread,ReentrantReadWriteLock,编程,sync,读锁,线程,public
From: https://blog.51cto.com/u_15870611/5837719

相关文章

  • Java中String被称为不可变字符串的原因
    很多东西,看似可变,实际上不过是是新桃换旧符罢了。 代码:/***String之所以被称为不可变字符串*/staticvoidtestString(){Stringstr="i......
  • mac下java和mvn的环境配置
    原文:https://blog.csdn.net/w605283073/article/details/111770386   https://www.pudn.com/news/62f8c6905425817ffc462029.htmlmvn打包报错:Nocompilerisprov......
  • LINQ编程总结
        LINQtoSQL查询表达式适用于关系数据库,查询表达式是基于对象的,它要求将数据库表和试图映射到实体,LINQTOSQL让数据库的操作变得更加的简单,完全弱化了数据库......
  • 计算机网络应用层:DNS、P2P和Socket编程
    DNS域名系统(DomainNameSystem,DNS)的主要任务是主机名到IP地址的转换的目录服务。DNS是:一个由分层DNS服务器实现的分布式数据库;一个使得主机能够查询分布式数据库的应......
  • JAVA遍历Map所有元素
    //JDK1.5Mapmap=newHashMap();for(Objectobj:map.keySet()){Objectkey=obj;Objectvalue=map.get(obj);}123456//JDK1.4......
  • java 串口工具jSerialComm
    由于项目之前用的串口工具RXTX只支持到jdk1.8然后项目目前用到的jdk是13的所以在网上找了一下找到了这个 jSerialComm目前使用是支持13及1.8的没做其它jdk版本测试......
  • Java线程安全
    线程安全的本质其实第一张图的例子是有问题的,主内存中的变量是共享的,所有线程都可以访问读写,而线程工作内存又是线程私有的,线程间不可互相访问。那在多线程场景下,图上的线程......
  • Caused by: java.lang.NoClassDefFoundError: net/minidev/asm/FieldFilter 报错的解
    Causedby:org.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'requestMappingHandlerAdapter'definedinclasspathresourc......
  • JavaScript 清空对象的值
      functionclearObj(obj){//判断是不是对象if(!Object.prototype.toString.call(obj)=="[objectObject]"){returnfalse;}for(constkey......
  • 函数式编程
    FunctionalInterface我们把只定义了单方法的接口称之为FunctionalInterface,用注解@FunctionalInterface标记。例如,Callable接口:@FunctionalInterfacepublicinterfaceC......