读写锁解读:https://www.jb51.net/article/145099.htm
package com.roocon.thread.ta3;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Demo {
private Map<String, Object> map = new HashMap<>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();//可重入的读写锁类
private Lock r = rwl.readLock();
private Lock w = rwl.writeLock();
public Object get(String key) {
r.lock();
System.out.println(Thread.currentThread().getName() + " read going..");
try {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return map.get(key);
} finally {
r.unlock();
System.out.println(Thread.currentThread().getName() + " read end..");
}
}
public void put(String key, Object value) {
w.lock();
System.out.println(Thread.currentThread().getName() + " write going..");
try {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
} finally {
w.unlock();
System.out.println(Thread.currentThread().getName() + " write end..");
}
}
}
------------------------------------------------------25--------------------------------------------------------ta3-------------------------------------------
读锁:是共享锁多个线程同时进入,读写互斥。
写锁:排他锁,同一时刻只能有一个线程进入。
读写锁的实现:
ReadWriteLock是一个接口,由ReentrantReadWriteLock实现。
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
WriteLock和ReadLock是ReentrantReadWriteLock的内部类。
开始就决定同步器是公平的还是非公平的。
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
创建,声明两个内部类的:
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
内部类实现Lock接口
public static class ReadLock implements Lock, java.io.Serializable {
内部类声明一个同步器,构造方法传入同步器,是外部类的同步器:
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
外部类的构造方法,在这里传入的,传的是非公平的锁:
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock() {
this(false);
}
读写锁是非公平的。不一定非要先加锁再先解锁。
公平和非公平锁:https://www.jianshu.com/p/f95838f6ee63
true和fasle是公平的还是非公平的。
读锁调用readLock的lock();
public void lock() {
sync.acquireShared(1);
}
调用的是外部类的acquireShared方法。这个是读锁是一把共享锁。
再看writeLock:
public void lock() {
sync.acquire(1);
}
同样调用同步器的acquire方法。
其实最后把加锁的方法都委托给同步器。
最后同步器创建的是取决于公平和非公平。
还有三个内部类,一个父类的同步器,一个公平的同步器,一个非公平的同步器:
abstract static class Sync extends AbstractQueuedSynchronizer {
上面那个是AQS
static final class NonfairSync extends Sync {
static final class FairSync extends Sync {
几个重要的方法:
百度搜索JDK文档:
写锁有一个读锁是有多个的,读锁的个数不是0,写锁是拿不到的。
1个int类型4个字节,32位数。
1111 1111 1111 1111 1111 1111 1111 1111,前16位表示一种锁的状态(次数),同理后16位。
看tryAcquire方法,这个是ReentrantReadWriteLock下的SYNC的方法,写锁的lock调用tryAcquire方法:
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();//总次数
int w = exclusiveCount(c);//独占锁重入的次数
if (c != 0) {//c!=0就是不是第一次进来所谓的重入
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;//此时肯定不是当前线程
if (w + exclusiveCount(acquires) > MAX_COUNT)//写锁重入的次数大于2^16-1=65535,写锁支持重入的次数就是65535
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);//重入成功+1
return true;
}
if (writerShouldBlock() || //第一次进来
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);//把线程设置为当前的线程
return true;
}
初始走的方法:c==0看这个方法writerShouldBlock()返回值永远是false,当前看的是非公平的。
重入走的方法:c!=0 c是所有锁重入的次数。
其中w是独占锁重入放入次数。
compareAndSetState:调回cas自旋等待。
private static final long serialVersionUID = 6317671515068378041L;
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
低位存的是写锁进入的次数,高位存的是读锁。
java的移位运算符:https://www.cnblogs.com/steven520213/p/8602785.html
看tryRelease方法:
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())//是否是独占锁
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);//为0为空
setState(nextc);//不为0记录还有进去几个
return free;
}
-----------------------共享锁的问题
分析。
看tryAcquireShared方法:
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&//如果独占锁不为0有写的线程返回-1
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);//获取共享锁重入的次数
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {//可以拿到锁
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {//判断是重入还是有另外的线程进来
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;//重入的值+1
}
return 1;
}
return fullTryAcquireShared(current);
}
保存每个读锁重入的次数的类。
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
如何保证线程安全?是ThreadLocal的。
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
不停的获取拿到返回值:
fullTryAcquireShared(current);
看tryReleaseShared
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
---------------------26----------------------
案例分析:
如何对这个操作加锁?
重点:在1加读锁是为了在2在写锁unlock之后不让加写锁进程进来了,读锁进来。
总结:在写锁释放掉之前加读锁,防止写锁再来不能读。
锁的降级:
只有读读不是互斥的。
写锁释放完毕后所有的写线程竞争写锁。加读锁是为了不让写锁竞争降级为读锁。
写锁降级为读锁,操作就是在写锁没有释放时候,获取读锁,再释放写锁。
为什么可以这样呢:
https://segmentfault.com/q/1010000017046276/a-1020000017047583
这不就是读写锁的目的吗?写锁是排他锁,在一个线程持有写锁时,可以保证没有任何其他线程持有锁,相当于独占,所以它可以再持有读锁。但是反过来却不行,因为读锁并不是排他的,其他线程可能正在持有读锁或写锁,所以不能随意升级
锁的升级,读锁升级为写锁,在读锁没有释放的时候获取写锁再释放读锁。-----不支持锁的升级。
代码:
package com.roocon.thread.ta5;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.imageio.spi.IIOServiceProvider;
public class Demo {
private Map<String, Object> map = new HashMap<>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
private Lock r = rwl.readLock();
private Lock w = rwl.writeLock();
private volatile boolean isUpdate;
public void readWrite() {
r.lock(); // 为了保证isUpdate能够拿到最新的值,先读要拿到读锁
if (isUpdate) {
r.unlock();//写先释放读锁
w.lock();
map.put("xxx", "xxx");
r.lock();
w.unlock();
}
Object obj = map.get("xxx");
System.out.println(obj);
r.unlock();
}
public Object get(String key) {
r.lock();
System.out.println(Thread.currentThread().getName() + " 读操作在执行..");
try {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return map.get(key);
} finally {
r.unlock();
System.out.println(Thread.currentThread().getName() + " 读操执行完毕..");
}
}
public void put(String key, Object value) {
w.lock();
System.out.println(Thread.currentThread().getName() + " 写操作在执行..");
try {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
} finally {
w.unlock();
System.out.println(Thread.currentThread().getName() + " 写操作执行完毕..");
}
}
}
在读的时候不是那么纯粹,要根据状态是不是在读的时候写。
对这个加锁。
先读的话首先要加读锁。
这个是无法保证线程安全的。
------------------------------27-------ta5-------