读写锁的概念

在高并发场景时,为了保证线程的安全。

  • 读-读可以并发执行
  • 读-写,写-读,写写都需要加锁执行。

而读写锁正好为了处理这种场景而生的,读写锁可用于读多写少的情况,读写锁是一对锁,它可以允许多个线程同时读,写操作由一个线程完成。

对于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

 为了看到读写锁的效率和好处,我们将读写锁全部改为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

从执行结果来看,在有多线程读取的时候,读写锁效率高于锁ReentrantLock。

 读写锁的应用场景

通过前面的例子而知在java中,读写锁适用于并发较高且读多写少的情况。