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;
        }