读写锁的概念
在高并发场景时,为了保证线程的安全。
- 读-读可以并发执行
- 读-写,写-读,写写都需要加锁执行。
而读写锁正好为了处理这种场景而生的,读写锁可用于读多写少的情况,读写锁是一对锁,它可以允许多个线程同时读,写操作由一个线程完成。
对于synchronized 关键字和ReentrantLock只有1把锁,多个线程不能同时进行read操作,这样在高并发读多写少的情况就不及读写锁。
读写锁的例子
这里对ArrayList使用读写锁,重写add,get,remove方法,如下:
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class HelloWorld{
public static long getTimeStamp(){
Date date = new Date();
long timestamp = date.getTime();//返回13位时间戳
return timestamp;
}
public static void main(String[] args) throws InterruptedException {
RWArrayList rwArrayList = new RWArrayList();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//添加100个数
for (int i=0; i<10; i++) {
rwArrayList.add(i);
}
System.out.println("添加数据完成");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//读
long time1 = getTimeStamp();
for (int i=0; i<rwArrayList.size(); i++) {
rwArrayList.get(i);
}
long time2 = getTimeStamp();
System.out.println("线程2 " + Thread.currentThread().getName() + ",时间差值 "+(time2-time1));
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
//读
long time1 = getTimeStamp();
for (int i=0; i<rwArrayList.size(); i++) {
rwArrayList.get(i);
}
long time2 = getTimeStamp();
System.out.println("线程3 " + Thread.currentThread().getName() + ",时间差值 "+(time2-time1));
}
});
// Thread t4 = new Thread(new Runnable() {
// @Override
// public void run() {
// //写
// long time1 = getTimeStamp();
// for (int i=0;i<5;i++) {
// rwArrayList.removeLast();
// }
// long time2 = getTimeStamp();
// System.out.println("线程4 " + Thread.currentThread().getName() + ",时间差值 "+(time2-time1));
// }
// });
//开启3个线程
System.out.println("读写锁");
t1.start();
t2.start();
t3.start();
t1.join();
// t4.start();
}
}
class RWArrayList<E> {
private ArrayList<E> list = new ArrayList<E>();
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
public void add(E element) {
try {
writeLock.lock();
list.add(element);
} finally {
writeLock.unlock();
}
}
public void removeLast() {
try {
writeLock.lock();
if(list.size()!=0){
list.remove(list.size() - 1);
}
} finally {
writeLock.unlock();
}
}
public void set(int index, E element) {
try {
writeLock.lock();
list.set(index, element);
} finally {
writeLock.unlock();
}
}
public int size() {
try {
// 读锁
readLock.lock();
return list.size();
} finally {
readLock.unlock();
}
}
public E get(int index) {
try {
// 读锁
readLock.lock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return list.get(index);
} finally {
readLock.unlock();
}
}
}
输出
读写锁
添加数据完成
线程2 Thread-1,时间差值 1104
线程3 Thread-2,时间差值 1104
添加数据完成
线程2 Thread-1,时间差值 1104
线程3 Thread-2,时间差值 1104
为了看到读写锁的效率和好处,我们将读写锁全部改为ReentLock锁。如下
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;
public class Test2{
public static long getTimeStamp(){
Date date = new Date();
long timestamp = date.getTime();//返回13位时间戳
return timestamp;
}
public static void main(String[] args) throws InterruptedException {
RWArrayList2 rwArrayList = new RWArrayList2();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//添加1000个数
for (int i=0; i<10; i++) {
rwArrayList.add(i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//读
long time1 = getTimeStamp();
for (int i=0; i rwArrayList.get(i);
}
long time2 = getTimeStamp();
System.out.println("线程2 " + Thread.currentThread().getName() + ",时间差值 "+(time2-time1));
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
//读
long time1 = getTimeStamp();
for (int i=0; i rwArrayList.get(i);
}
long time2 = getTimeStamp();
System.out.println("线程3 " + Thread.currentThread().getName() + ",时间差值 "+(time2-time1));
}
});
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
//写
long time1 = getTimeStamp();
for (int i=0;i<5;i++) {
rwArrayList.removeLast();
}
long time2 = getTimeStamp();
System.out.println("线程4 " + Thread.currentThread().getName() + ",时间差值 "+(time2-time1));
}
});
//开启3个线程
System.out.println("一般锁");
t1.start();
t2.start();
t3.start();
t1.join();
// t4.start();
}
}
class RWArrayList2 {
private ArrayList list = new ArrayList();
ReentrantLock lock = new ReentrantLock();
public void add(E element) {
try {
lock.lock();
list.add(element);
} finally {
lock.unlock();
}
}
public void removeLast() {
try {
lock.lock();
if(list.size()!=0){
list.remove(list.size() - 1);
}
} finally {
lock.unlock();
}
}
public void set(int index, E element) {
try {
lock.lock();
list.set(index, element);
} finally {
lock.unlock();
}
}
public int size() {
try {
// 读锁
lock.lock();
return list.size();
} finally {
lock.unlock();
}
}
public E get(int index) {
try {
// 读锁
lock.lock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return list.get(index);
} finally {
lock.unlock();
}
}
}
输出一般锁
线程2 Thread-1,时间差值 1099
线程3 Thread-2,时间差值 2203
线程2 Thread-1,时间差值 1099
线程3 Thread-2,时间差值 2203
从执行结果来看,在有多线程读取的时候,读写锁效率高于锁ReentrantLock。
读写锁的应用场景
通过前面的例子而知在java中,读写锁适用于并发较高且读多写少的情况。