宝塔服务器面板,一键全能部署及管理,送你10850元礼包,点我领取

排他锁与共享锁

读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。

读-读能共存,读-写不能共存,写-写不能共存

读写锁的实现

 1 public class Demo {
 2     private Map<String,Object> map = new HashMap<>();
 3     private ReadWriteLock rw = new ReentrantReadWriteLock();
 4 
 5     private Lock r = rw.readLock();
 6     private Lock w = rw.writeLock();
 7 
 8 
 9 
10     public Object get(String key){
11         r.lock();
12         System.out.println(Thread.currentThread().getId()+"读操作正在执行");
13         try {
14             try {
15                 Thread.sleep(1000);
16             } catch (InterruptedException e) {
17                 e.printStackTrace();
18             }
19             return map.get(key);
20         }finally {
21             r.unlock();
22             System.out.println(Thread.currentThread().getName()+"读操作执行完毕");
23         }
24     }
25 
26     public void put(String key ,Object value){
27             w.lock();
28         System.out.println(Thread.currentThread().getName()+"写操作正在执行");
29         try {
30             try {
31                 Thread.sleep(1000);
32             } catch (InterruptedException e) {
33                 e.printStackTrace();
34             }
35          map.put(key,value);
36         }finally {
37             w.unlock();
38             System.out.println(Thread.currentThread().getName()+"写操作执行完毕");
39         }
40     }
41 
42 
43 }

Main

 1 public class Main {
 2 
 3     public static void main(String[] args) {
 4         Demo d = new Demo();
 5         new Thread(new Runnable() {
 6             @Override
 7             public void run() {
 8                 d.put("a",10);
 9 
10             }
11         }).start();
12 
13         new Thread(new Runnable() {
14             @Override
15             public void run() {
16                 d.put("",10);
17 
18             }
19         }).start();
20         new Thread(new Runnable() {
21             @Override
22             public void run() {
23                 d.put("c",10);
24 
25             }
26         }).start();
27 
28     }
29 
30 }

执行如下

Thread-0写操作正在执行
Thread-0写操作执行完毕
Thread-1写操作正在执行
Thread-1写操作执行完毕
Thread-2写操作正在执行
Thread-2写操作执行完毕

读锁

 1 public class Main2 {
 2 
 3     public static void main(String[] args) {
 4         Demo d = new Demo();
 5         d.put("key1","value1");
 6 
 7         new Thread(new Runnable() {
 8             @Override
 9             public void run() {
10                 System.out.println(d.get("key1"));
11             }
12         }).start();
13 
14         new Thread(new Runnable() {
15             @Override
16             public void run() {
17                 System.out.println(d.get("key1"));
18             }
19         }).start();
20         new Thread(new Runnable() {
21             @Override
22             public void run() {
23                 System.out.println(d.get("key1"));
24             }
25         }).start();
26 
27     }
28 }

执行如下,速度比写锁块。

Thread-1读操作执行完毕
Thread-0读操作执行完毕
value1
value1
Thread-2读操作执行完毕
value1

ReentranReadWriteLock

WriteLock implements Lock, java.io.Serializable

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

调用同步器的acquire()方法,独占排他锁

同步器创建取决于是公平的和非公平的,依然有三个内部类,Sync,NonfairSync,FairSync,

Sync继承了AQS,NonfairSync,FairSync继承了Sync

读写锁需要保存的状态,(int值表示重入的次数)

写锁重入的次数

读锁的个数

每个读锁重入的次数

较关注的就是tryAcquire,tryRelease,tryAcquiredShared,tryReleaseShared

1.

 1         protected final boolean tryAcquire(int acquires) {
 2             /*
 3              * Walkthrough:
 4              * 1. If read count nonzero or write count nonzero
 5              *    and owner is a different thread, fail.
 6              * 2. If count would saturate, fail. (This can only
 7              *    happen if count is already nonzero.)
 8              * 3. Otherwise, this thread is eligible for lock if
 9              *    it is either a reentrant acquire or
10              *    queue policy allows it. If so, update state
11              *    and set owner.
12              */
13             Thread current = Thread.currentThread();
14             int c = getState();
15             int w = exclusiveCount(c);
16             if (c != 0) {
17                 // (Note: if c != 0 and w == 0 then shared count != 0)
18                 if (w == 0 || current != getExclusiveOwnerThread())
19                     return false;
20                 if (w + exclusiveCount(acquires) > MAX_COUNT)
21                     throw new Error("Maximum lock count exceeded");
22                 // Reentrant acquire
23                 setState(c + acquires);
24                 return true;
25             }
26             if (writerShouldBlock() ||
27                 !compareAndSetState(c, c + acquires))
28                 return false;
29             setExclusiveOwnerThread(current);
30             return true;
31         }

先看锁不重入的情况

writerShouldBlock() 返回 false,执行 compareAndSetState(c, c + acquires)) 

状态设置为1,返回 fasle,

最后把线程设置为当前线程 , setExclusiveOwnerThread(current);

w == 0, 说明是读锁请求这个方法。直接返回flase.

w + exclusiveCount(acquires) > MAX_COUNT ,w,写锁重入的次数+1 > MAX_COUNT

(1 << SHARED_SHIFT) – 1 = 65535,抛出异常。

否则重入成功  ,setState(c + acquires); 状态+1,返回true

2.

 1         protected final boolean tryRelease(int releases) {
 2             if (!isHeldExclusively())
 3                 throw new IllegalMonitorStateException();
 4             int nextc = getState() - releases;
 5             boolean free = exclusiveCount(nextc) == 0;
 6             if (free)
 7                 setExclusiveOwnerThread(null);
 8             setState(nextc);
 9             return free;
10         }

首先判断是否是独占锁,不是抛出异常

是的话 getState() – releases; 状态 -1

exclusiveCount(nextc) == 0; 独占的数量是否为0 ,有,返回false,

没有,返回true,exclusiveCount(nextc) == 0; 把当前线程置为空

serState(nextc);保存0或-1的值

3.

 1         protected final int tryAcquireShared(int unused) {
 2             /*
 3              * Walkthrough:
 4              * 1. If write lock held by another thread, fail.
 5              * 2. Otherwise, this thread is eligible for
 6              *    lock wrt state, so ask if it should block
 7              *    because of queue policy. If not, try
 8              *    to grant by CASing state and updating count.
 9              *    Note that step does not check for reentrant
10              *    acquires, which is postponed to full version
11              *    to avoid having to check hold count in
12              *    the more typical non-reentrant case.
13              * 3. If step 2 fails either because thread
14              *    apparently not eligible or CAS fails or count
15              *    saturated, chain to version with full retry loop.
16              */
17             Thread current = Thread.currentThread();
18             int c = getState();
19             if (exclusiveCount(c) != 0 &&
20                 getExclusiveOwnerThread() != current)
21                 return -1;
22             int r = sharedCount(c);
23             if (!readerShouldBlock() &&
24                 r < MAX_COUNT &&
25                 compareAndSetState(c, c + SHARED_UNIT)) {
26                 if (r == 0) {
27                     firstReader = current;
28                     firstReaderHoldCount = 1;
29                 } else if (firstReader == current) {
30                     firstReaderHoldCount++;
31                 } else {
32                     HoldCounter rh = cachedHoldCounter;
33                     if (rh == null || rh.tid != getThreadId(current))
34                         cachedHoldCounter = rh = readHolds.get();
35                     else if (rh.count == 0)
36                         readHolds.set(rh);
37                     rh.count++;
38                 }
39                 return 1;
40             }
41             return fullTryAcquireShared(current);
42         }

当读线程,能拿成功,写线程,拿不成功

19             if (exclusiveCount(c) != 0 &&
20                 getExclusiveOwnerThread() != current)
21                 return -1;

独占锁不为0,有写线程,并不是当前线程,return -1 拿不到

如果不阻塞,小于MAX_COUNT 并且设置成功,即能够进来

r==0,第一次进来,firstReaderHoldCount = 1;
private transient int firstReaderHoldCount; --- int类型的值

如果不等于0,firstReaderHoldCount++
如果两者都不满足,说明并非重入,而是有另外的线程进来

        static final class HoldCounter {
            int count = 0;
            // Use id, not reference, to avoid garbage retention
            final long tid = getThreadId(Thread.currentThread());
        }

HoldCounter 记录重入的次数,保存在当前线程,保存在ThreadLocal,保证线程安全性

1         static final class ThreadLocalHoldCounter
2             extends ThreadLocal<HoldCounter> {
3             public HoldCounter initialValue() {
4                 return new HoldCounter();
5             }
6         }

如果 rh == null   rh.count++

4

 1         protected final boolean tryReleaseShared(int unused) {
 2             Thread current = Thread.currentThread();
 3             if (firstReader == current) {
 4                 // assert firstReaderHoldCount > 0;
 5                 if (firstReaderHoldCount == 1)
 6                     firstReader = null;
 7                 else
 8                     firstReaderHoldCount--;
 9             } else {
10                 HoldCounter rh = cachedHoldCounter;
11                 if (rh == null || rh.tid != getThreadId(current))
12                     rh = readHolds.get();
13                 int count = rh.count;
14                 if (count <= 1) {
15                     readHolds.remove();
16                     if (count <= 0)
17                         throw unmatchedUnlockException();
18                 }
19                 --rh.count;
20             }
21             for (;;) {
22                 int c = getState();
23                 int nextc = c - SHARED_UNIT;
24                 if (compareAndSetState(c, nextc))
25                     // Releasing the read lock has no effect on readers,
26                     // but it may allow waiting writers to proceed if
27                     // both read and write locks are now free.
28                     return nextc == 0;
29             }
30         }

  

锁降级就是把写锁降级为读锁
在写锁没有释放的时候,获取到读锁,再释放写锁

 1 private Map<String,Object> map = new HashMap<String,Object>();
 2     //读写锁
 3     private ReadWriteLock lock  =new ReentrantReadWriteLock();
 4     //读锁
 5     private Lock readLock = lock.readLock();
 6     //写锁
 7     private Lock writeLock =lock.writeLock();
 8     
 9     private volatile boolean flag;
10     
11     public void readwrite() {
12         //为了保证flag能拿到最新的值 
13         readLock.lock();
14         if(flag) {
15             //对值进行写操作,因为读写锁互斥,若不释放读锁,则写锁无法获取
16             readLock.unlock();
17             //获取写锁     读锁释放完毕后,所有写锁竞争线程
18             writeLock.lock(); 
19             //写锁是排它锁,最终有一个线程获得写锁,并执行put写操作
20             map.put("hello", "hi");
21             //在写完后,若不加读锁,则直接释放读锁,其他线程可能同样进行put()写操作
22             //在此加了读锁后,读写锁是互斥的,其他线程必须等待readLock读锁释放后才能写(put )成功
23             readLock.lock();   //获取读锁进行锁降级
24             //释放写锁
25             writeLock.unlock();
26         }
27         Object value = map.get("hello");
28         System.out.println(value);
29         readLock.unlock();
30     }

锁降级的作用

1.保证同一线程内数据的可见性。 2.提高并发的性能