Synchornized优化总结(二)
前面讲了 Java 系统是如何针对内部锁进行优化的。如果说内部锁的优化是 Java 系统自身完成的话,那么接下来的优化就需要通过代码实现了。
锁的开销主要是在争用锁上,当多线程对共享资源进行访问时,会出现线程等待。
即便是使用内存屏障,也会导致冲刷写缓冲器,清空无效化队列等开销。
为了降低这种开销,通常可以从几个方面入手,例如:减少线程申请锁的频率(减少临界区)和减少线程持有锁的时间长度(减小锁颗粒)以及多线程的设计模式。
减少临界区的范围
当共享资源需要被多线程访问时,会将共享资源或者代码段放到临界区中。

减小锁的颗粒度

- addUser
- addQuery
- removeUser
- removeQuery

假设,当一个线程池调用 addUser 方法的时候,只会锁住 user 对象。另外一个线程是可以执行 addQuery 和 removeQuery 方法的。

读写锁
也叫做线程的读写模式(Read-Write Lock),其本质是一种多线程设计模式。
将读取操作和写入操作分开考虑,在执行读取操作之前,线程必须获取读取的锁。
在执行写操作之前,必须获取写锁。当线程执行读取操作时,共享资源的状态不会发生变化,其他的线程也可以读取。但是在读取时,不可以写入。
其实,读写模式就是将原来共享资源的锁,转化成为读和写两把锁,将其分两种情况考虑。



- Reader(读者),对 SharedResource 角色执行 Read 操作。
- Writer(写者),对 SharedResource 角色执行 Write 操作。
- SharedResource(共享资源),表示对 Reader 和 Writer 两者共享的资源。
- ReadWriteLock(读写锁),提供了 SharedResource 角色实现 Read 操作和 Write 操作时所需的锁。
针对 Read 操作提供 readLock 和 readUnlock,对 Write 操作提供 writeLock 和 writeUnlock。
特别需要注意的是,在这里需要解决读写冲突的问题。当线程 A 获取读锁时,如果有线程 B 正在执行写操作,线程 A 需要等待,否则会引起 read-write conflict(读写冲突)。
如果线程 B 正在执行读操作,线程 A 不需要等待,因为 read-read 不会引起 conflict(冲突)。
当线程 A 要获取写入锁时,线程 B 正在执行写操作,线程 A 需要等待,否则会引起 write-write conflict(写写冲突)。

我们通过 Data 类 SharedResource,ReaderThread 和 WriterThread 来实现 Reader 和 Writer,ReadWriteLock 类来实现读写锁。


- readingReaders:正在读取共享资源的线程个数,整型。
- waitingWriters:正在等待写入共享资源的线程个数,整型。
- writingWriters:正在写入共享资源的线程个数,整型。
- preferWriter:写入优先级标示,布尔型,为 true 表示写入优先;为 false 表示读取优先。
里面包含了四个方法,分别是:
- readLock
- readUnlock
- writeLock
- writeUnlock

- readLock,读锁。线程在读的时候,检查是否有写线程在执行,如果有就需要等待。同时还会观察,在写入优先的时候,是否有等待写入的线程。
如果存在也需要等待,等待写入操作的线程完成再执行。如果以上条件都没有满足,那么进行读操作,并将读取线程数 +1。
- readUnlock,读解锁。线程在读操作完成以后,将读取线程数 -1。通知其他等待线程执行。
- writeLock,写锁。先将写等待线程数 +1。如果发现有正在读的线程或者有正写的线程,那么进入等待。否则,进行写操作,并将正在写操作线程数 +1。
- writeUnlock,写解锁。线程在写操作完成以后,将写线程数 -1。通知其他等待线程执行。
又例如:在做写操作(doWrite)之前需要加上 writeLock(写锁),在完成写操作以后释放写锁(writeUnlock)。
