题目
ReentrantReadWriteLock会发生写饥饿的情况吗?如果发生,有没有比较好的解决办法?
解答
1、ReentrantReadWriteLock会发生写饥饿的情况吗?
因为可以允许多个线程同时读,如果高并发情况下,读操作获取到读锁后,就可能出现一直不断的有读操作进入临界区,一直不释放读锁,造成写操作阻塞,所以可以通过高并发读操作来确定ReentrantReadWriteLock是否会发生写饥饿情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| package readwritelock;
import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo { private static final SimpleDateFormat DF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); private List<String> dataList = new ArrayList<String>(); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private String getDataByIndex(int index) { System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "] [Try Read] index : " + index); lock.readLock().lock(); System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "] [Get Read] index : " + index); try { if (dataList.size() <= index) { return ""; } return dataList.get(index); } finally { lock.readLock().unlock(); } }
private void addData(int index, String data) { System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "] [Try Write] Index: " + index + " data : " + data); lock.writeLock().lock(); System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "] [Get Write] Index: " + index + " data : " + data); try { dataList.add(data); } finally { lock.writeLock().unlock(); } }
private void loopRead(final int readTimes) { ExecutorService executorService = Executors.newFixedThreadPool(20); for (int i = 0; i < readTimes; i++) { final int finalI = i; executorService.submit(new Runnable() { public void run() { sleep(); String data = getDataByIndex(finalI); System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "] [Read Success] Index:" + finalI + ",Data:" + data); } }); } }
private void loopWrite(final int writeTimes) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < writeTimes; i++) { final int finalI = i; executorService.submit(new Runnable() { public void run() { sleep(); String data = DF.format(new Date()); addData(finalI, data); System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "]" + " [Write Success] Index: " + finalI + " Data:" + data); } }); } }
private void sleep() { long l = System.currentTimeMillis(); int sleepTime = (int) l % 5; try { Thread.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } }
public static void main(String[] args) { ReadWriteLockDemo demo = new ReadWriteLockDemo(); demo.loopRead(100000); demo.loopWrite(100); } }
|
部分运行日志如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| [2019-09-28 17:02:01 083][pool-2-thread-2] [Try Write] Index: 1 data : 2019-09-28 17:02:01 083 [2019-09-28 17:02:01 083][pool-2-thread-1] [Try Write] Index: 0 data : 2019-09-28 17:02:01 083 [2019-09-28 17:02:01 084][pool-1-thread-8] [Try Read] index : 701 [2019-09-28 17:02:01 084][pool-1-thread-10] [Try Read] index : 700 [2019-09-28 17:02:01 084][pool-1-thread-8] [Get Read] index : 701 [2019-09-28 17:02:01 084][pool-1-thread-8] [Read Success] Index:701,Data: [2019-09-28 17:02:01 084][pool-1-thread-5] [Try Read] index : 698 [2019-09-28 17:02:01 084][pool-1-thread-9] [Try Read] index : 699 [2019-09-28 17:02:01 084][pool-2-thread-6] [Try Write] Index: 5 data : 2019-09-28 17:02:01 084 [2019-09-28 17:02:01 084][pool-2-thread-5] [Try Write] Index: 4 data : 2019-09-28 17:02:01 084 [2019-09-28 17:02:01 084][pool-1-thread-9] [Get Read] index : 699 [2019-09-28 17:02:01 084][pool-1-thread-15] [Try Read] index : 697 [2019-09-28 17:02:01 084][pool-1-thread-9] [Read Success] Index:699,Data: …… [2019-09-28 17:02:01 090][pool-2-thread-4] [Try Write] Index: 3 data : 2019-09-28 17:02:01 090 [2019-09-28 17:02:01 090][pool-2-thread-2] [Get Write] Index: 1 data : 2019-09-28 17:02:01 083 [2019-09-28 17:02:01 090][pool-2-thread-3] [Try Write] Index: 2 data : 2019-09-28 17:02:01 090 [2019-09-28 17:02:01 090][pool-2-thread-1] [Get Write] Index: 0 data : 2019-09-28 17:02:01 083 [2019-09-28 17:02:01 090][pool-2-thread-2] [Write Success] Index: 1 Data:2019-09-28 17:02:01 083 [2019-09-28 17:02:01 090][pool-2-thread-5] [Get Write] Index: 4 data : 2019-09-28 17:02:01 084 [2019-09-28 17:02:01 090][pool-2-thread-1] [Write Success] Index: 0 Data:2019-09-28 17:02:01 083
|
可以看到 Index = 1的写线程虽然早于 Index = 701的读线程去尝试获取锁,但直到7毫秒后在没有读操作的时候,才获取到写锁;由此可以看出,ReentrantReadWriteLock确实会发生写饥饿,在读操作并发量越大时,越不容易获取到写锁。
2、那怎么让写操作优先于读操作?即怎么解决写饥饿情况?
很明显,我们可以在每次读操作前,判断有没有写操作在排队等候获取写锁,如果有,则阻塞当前线程,使写操作优先执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| package readwritelock;
import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo { private static final SimpleDateFormat DF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); private List<String> dataList = new ArrayList<String>(); private final AtomicInteger writeNum = new AtomicInteger(0); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private String getDataByIndex(int index) { System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "] [Try Read] index : " + index); while (writeNum.get() > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } lock.readLock().lock(); System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "] [Get Read] index : " + index); try { if (dataList.size() <= index) { return ""; } return dataList.get(index); } finally { lock.readLock().unlock(); } }
private void addData(int index, String data) { System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "] [Try Write] Index: " + index + " data : " + data); writeNum.incrementAndGet(); lock.writeLock().lock(); System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "] [Get Write] Index: " + index + " data : " + data); try { writeNum.decrementAndGet(); dataList.add(data); } finally { lock.writeLock().unlock(); } }
private void loopRead(final int readTimes) { ExecutorService executorService = Executors.newFixedThreadPool(20); for (int i = 0; i < readTimes; i++) { final int finalI = i; executorService.submit(new Runnable() { public void run() { sleep(); String data = getDataByIndex(finalI); System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "] [Read Success] Index:" + finalI + ",Data:" + data); } }); } }
private void loopWrite(final int writeTimes) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < writeTimes; i++) { final int finalI = i; executorService.submit(new Runnable() { public void run() { sleep(); String data = DF.format(new Date()); addData(finalI, data); System.out.println("[" + DF.format(new Date()) + "][" + Thread.currentThread().getName() + "]" + " [Write Success] Index: " + finalI + " Data:" + data); } }); } }
private void sleep() { long l = System.currentTimeMillis(); int sleepTime = (int) l % 5; try { Thread.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } }
public static void main(String[] args) { ReadWriteLockDemo demo = new ReadWriteLockDemo(); demo.loopRead(100000); demo.loopWrite(100); } }
|
在代码18行处,我们增加了一个原子类变量writeNum,用来标示当前正在排队的写操作线程数,当writeNum<=0 时,才继续执行当前读操作。
部分日志如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| [2019-09-28 17:35:29 437][pool-1-thread-9] [Try Read] index : 659 [2019-09-28 17:35:29 437][pool-1-thread-9] [Get Read] index : 659 [2019-09-28 17:35:29 437][pool-1-thread-9] [Read Success] Index:659,Data: [2019-09-28 17:35:29 437][pool-2-thread-5] [Try Write] Index: 79 data : 2019-09-28 17:35:29 437 [2019-09-28 17:35:29 437][pool-1-thread-7] [Try Read] index : 661 [2019-09-28 17:35:29 437][pool-2-thread-5] [Get Write] Index: 79 data : 2019-09-28 17:35:29 437 [2019-09-28 17:35:29 437][pool-2-thread-5] [Write Success] Index: 79 Data:2019-09-28 17:35:29 437 [2019-09-28 17:35:29 437][pool-1-thread-18] [Try Read] index : 663 [2019-09-28 17:35:29 437][pool-1-thread-18] [Get Read] index : 663 [2019-09-28 17:35:29 437][pool-1-thread-18] [Read Success] Index:663,Data: …… [2019-09-28 17:35:29 447][pool-1-thread-7] [Get Read] index : 661 [2019-09-28 17:35:29 447][pool-1-thread-16] [Get Read] index : 695 [2019-09-28 17:35:29 448][pool-1-thread-7] [Read Success] Index:661,Data:
|
可以看出index = 661的读线程判断有index = 79的写线程排队时,便阻塞自己,使写线程优先执行,事实上可以看出index = 661的读线程获取到读锁的时间刚好是我们设置的10毫秒之后,因此有写操作排队的情况下,优先执行写操作,没有出现写饥饿。