1、锁基本概念
java并发为了线程安全需要对线程进行加锁,从而保证各线程安全地访问共享数据。但是加锁安全之后,又想提高加锁的效率。出现了共享锁和排它锁。
- 共享锁:同一资源允许多个线程对其进行操作,而且不会引起数据不安全(如脏数据、幻读等现象)
- 排它锁:同一资源允许一个线程对其进行操作,而且不会引起数据不安全
综上,共享锁的效率比排它锁高,但并不是所有场景都适用于共享锁。读写锁就是在某一场景下使用的;如一个文件可多个线程读,但只允许一个线程写。
2、读写锁概念
- 同一时刻允许多个读锁占用(共享锁)
- 同一时刻只允许一个线程占用(排它锁)
3、java内置读写锁(ReentrantReadWriteLock)
● ReentrantReadWriteLock特点○ 公平性:先请求锁的线程先获得锁(相对的有非公平性锁)
○ 可重入:一个已获得锁的线程可以继续再向该资源加锁
○ 锁降级:写锁可降级到读锁,即持有写锁的线程可放弃写锁,变为读锁
○ 实现ReadWriteLock接口
■ readLock()
■ writeLock()
● 读写锁依赖自定义同步器来实现同步功能,而读写状态就是同步器的同步状态
○ 同步状态(记为S):表示锁被一个线程重复获取的次数
■ 高16位表示读,低16位表示写
■ 通过位运算实现
● 访问写状态:S&0x0000FFFF
● 修改写状态:S+1
● 访问读状态:S>>>16
● 修改读状态:S+(1<<16)
○ 判断哪种锁:S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取
4、读写锁实例
/**
* 对hashMap进行读写操作,并保证HashMap线程安全
* @author hejy
*
*/
public class Cache {
static HashMap<Integer,Object> map=new HashMap<Integer,Object>();
ReentrantReadWriteLock rwl=new ReentrantReadWriteLock();
Lock r=rwl.readLock(); //获取读锁
Lock w=rwl.writeLock(); //获取写锁
//读
public Object get(int key) {
r.lock();
Object o=map.get(key);
r.unlock();
return o;
}
//写
public void set(int key,Object o) {
w.lock();
map.put(key, o);
w.unlock();
}
//清空锁
public void clear() {
w.lock();
map.clear();
w.unlock();
}
//读线程
class Read extends Thread{
int key;
public Read(int key) {
this.key=key;
}
@Override
public void run() {
// TODO Auto-generated method stub
// System.out.println("read "+Thread.currentThread().getName());
System.out.println("从HashMap读取:开始");
System.out.println(get(key));
System.out.println("从HashMap读取:结束");
}
}
//写线程
class Write extends Thread{
int key;
String str;
public Write(int key,String str) {
this.key=key;
this.str=str;
}
@Override
public void run() {
// TODO Auto-generated method stub
// System.out.println("write "+Thread.currentThread().getName());
System.out.println("向HashMap写入:开始");
set(key, str);
System.out.println("向HashMap写入:结束");
}
}
public static void main(String[] args) {
Cache141 c=new Cache141();
String str[]= {"a","b","c","d","e","f","g","h","i","j"};
for (int i = 0; i < 5; i++) {
c.new Write(i,str[i]).start();
c.new Read(i).start();
}
}
}
5、源码分析-获取写锁(tryAcquire(intacquires))
- 上面的“Lock w=rwl.writeLock(); //获取写锁对象,w.lock();则是获取写锁,下面一步步分析:
- 执行w.lock()其实是执行:
public void lock() {
sync.acquire(1);
}
- 可以看到其实是执行acquire(int arg)函数,该函数如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) && //获取资源
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //加入等待队列
selfInterrupt(); //自我中断
}
- 上面只对tryAcquire(int arg)函数分析【其他的函数在以后章节解析】,tryAcquire(int arg)是获取锁的意思,因为Lock接口对象指向WriteLock对象(写锁),所以这里是获取写锁
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread(); //当前线程
int c = getState(); //获取状态
int w = exclusiveCount(c); //获取写状态
if (c != 0) { //状态不为0,则有锁
// 如果写状态为0,则为读锁;
//否则写状态不为0,则为写锁,因为是重入锁,所以判断当前线程是否获写锁的线程
if (w == 0 || current != getExclusiveOwnerThread())
return false; //获锁失败
//重入次数大于最大次数,MAX_COUNT=2^16-1
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 更新状态,这里的acquires=1
setState(c + acquires);
return true; //获取成功
}
if (writerShouldBlock() || //该写锁是否应该阻塞
!compareAndSetState(c, c + acquires)) //CAS操作是否成功
return false;
setExclusiveOwnerThread(current); //当前线程获得锁
return true;
}