概念
读写自旋锁是一种结合了读写锁和自旋锁的锁类型,可以同时支持并发读和互斥写。
Java中可以使用ReentrantReadWriteLock类来实现读写可重入锁,该类提供了读锁和写锁两种类型的锁,都是可重入的。由于读是不对数据造成影响的,读锁是共享的,所以读锁被称为共享锁;由于写操作是对数据的修改,存在线程安全问题,为保证线程安全,写锁是独占的,所以写锁被称为独占锁。
示例
下面是使用ReentrantReadWriteLock实现读写锁的示例:
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void readData() {
lock.readLock().lock(); // 获取读锁
try {
// 读取共享数据
} finally {
lock.readLock().unlock(); // 释放读锁
}
}
public void writeData() {
lock.writeLock().lock(); // 获取写锁
try {
// 修改或写入数据
} finally {
lock.writeLock().unlock(); // 释放写锁
}
}
}
在上面的示例代码中,readData方法通过调用lock.readLock()方法获取读锁,而writeData方法则通过调用lock.writeLock()方法获取写锁。
使用读写锁时,在读取共享数据时可以并发进行,即多个线程可以同时对同一段共享数据进行读取,而在修改或写入共享数据时,只有一个线程能进行操作,其他线程需要等待其完成。
需要注意的是,在使用ReentrantReadWriteLock实现读写锁时,写锁拥有最高的优先级,一旦写锁被请求,那么任何读锁或写锁的请求都会被阻塞,直到写锁完全释放(写锁现参与者本身也可以申请读锁的,这种情况下,由于写锁仍然持有,读锁的申请仍然被阻塞)。
为什么不干脆把读操作设置为无锁?
那么这时候我们就会有一个疑问:既然ReentrantReadWriteLock的读锁是共享锁,那为什么不干脆把读操作设置为无锁?
想要弄清楚这个问题,首先要明白写锁的机制,写锁是一个独占锁,独占锁具有排它性,当一个线程持有写锁的时候,其他的线程所持有的读锁都会被阻塞(上面提到过)。
如果我们不为读操作加锁,那么当一个线程获取写锁在修改数据的时候,我们读到了这个修改之前的数据,然后线程修改完毕,我们会发现我们读取的内容和数据是对不上的,我们读出来和实际数据不一样的这种数据,称其为脏数据,这样就不具有同步性了(这个问题的本质和数据库脏读基本一致,后面我也会发布关于数据库的文章来详细介绍)。
但是,如果加上了读锁就不一样了,当有线程获取了写锁,在对数据进行修改的时候,其他线程获取读锁想要读取数据,就会被阻塞,等到写锁完全释放,持有读锁的线程才能读取。这样就保证了数据同步和避免读到脏数据的情况。