ReadWriteLock: 读写锁

ReadWriteLock:

JDK1.5提供的读写分离锁,采用读写锁分离可以有效帮助减少锁竞争。

特点:

1).使用读写锁。当线程只进行读操作时,可以允许多个线程同时读

2).写写操作,读写操作间依然需要相互等待和持有锁。

一).使用读写锁与使用重入锁进行读读写操作

开启200个线程,测试读写锁和重入锁的读效率。

使用重入锁进行读写操作:ReentrantLock_Rw

import java.util.concurrent.locks.ReentrantLock;

/**
 * 使用重入锁进行读写操作
 *    线程的读写使用同一把锁。
 */
public class ReentrantLock_RW {
    private static ReentrantLock lock = new ReentrantLock();
    private static int value ;

    //读操作
    public Object handleRead() throws InterruptedException {
        try {
            //获取锁
            lock.lock();
            //模拟读操作,读操作耗时越多,读写锁的优势越明显
            Thread.sleep(1);
            return value;
        }finally {
            lock.unlock();
        }
    }
    /**
     * 写操作
     */
    public void handleWrite(int i) throws InterruptedException {
        try {
            lock.lock();
            //模拟写操作
            Thread.sleep(1);
            value = i;
        }finally {
            lock.unlock();
        }
    }
}

使用ReadWriteLock进行读写操作: ReadWriteLock_RW

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 使用ReadWriteLock进行读写操作
 */
public class ReadWriteLock_RW {
    private static int value;
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();


    /**
            * 读取锁
    */
    private static Lock readLock = readWriteLock.readLock();

    /**
     * 写入锁
     */
    private static Lock writeLock = readWriteLock.writeLock();

    /**
     * 读操作
     */
    public Object handleRead() throws InterruptedException {
        try {
            readLock.lock();
            Thread.sleep(1);
            return value;
        }finally {
            //释放锁
            readLock.unlock();
        }

    }

    /**
     * 写操作
     */
    public void handleWrite(int index) throws InterruptedException {
        try{
            //获取锁
            writeLock.lock();
            Thread.sleep(1);
            value = index;
        }finally {
            //释放锁
            writeLock.unlock();
        }
    }

}

二)、测试多线程运行时间的知识储备

参考:https://www.cnblogs.com/jack-xsh/p/8615644.html

怎么测试多线程的运行时间?

//指定要开启的线程数
final static CountdownLatch countdownLatch = new CountdownLatch(200);
//每执行完一个线程,countdownLatch的线程数减一。
countdownLatch.countdown();
//挂起主线程,当cuntdown()的线程数为0,恢复主线程。
countdownLatch.await();

三)、使用重入锁进行读操作性能测试

重入锁读操作线程:ReentrantLockReadThread

**
 * 比较重入锁与读写锁读数据的性能
 */
public class ReentrantLockReadThread implements Runnable{
    //闭锁,线程计时工具
    private CountDownLatch countDownLatch;
    private  ReentrantLock_RW reentrantLockRw;
    public ReentrantLockReadThread(ReentrantLock_RW reentrantLockRw, CountDownLatch countDownLatch) {
        this.reentrantLockRw = reentrantLockRw;
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            reentrantLockRw.handleRead();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //线程执行完将线程挂起
            countDownLatch.countDown();
        }
    }
}

重入锁读线程效率:ReentrantLockReadThreadTest

public class ReentrantLockReadThreadTest {
    //程序计计时器,用于计算多线程的执行时间
    final static CountDownLatch countDownLatch = new CountDownLatch(200);
    //其实开启200个线程读取lock中值的速率
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock_RW reentrantLockRw = new ReentrantLock_RW();
        ReentrantLockReadThread readThread = new ReentrantLockReadThread(reentrantLockRw, countDownLatch);
        long time = System.currentTimeMillis();
        for(int i = 0; i < 200; i++){
            Thread t = new Thread(readThread);
            t.start();
        }
        //将主线程挂起
        countDownLatch.await();
        System.out.println(System.currentTimeMillis() - time);
    }
}

结果:

2194

四)、使用读写锁进行读操作的性能测试

读写锁读操作线程:ReadLockThread

import java.util.concurrent.CountDownLatch;

/**
 * 读入锁线程
 */
public class ReadLockThread implements Runnable{
    private ReadWriteLock_RW readWriteLockrw;
    private CountDownLatch countDownLatch;
    public ReadLockThread(ReadWriteLock_RW readWriteLockrw, CountDownLatch countDownLatch) {
        this.readWriteLockrw = readWriteLockrw;
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            readWriteLockrw.handleRead();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //线程执行完将该线程挂起
            countDownLatch.countDown();
        }
    }
}

读线程读写效率测试:ReadLockThreadTest

import java.util.concurrent.CountDownLatch;

/**
 * 测试使用ReadLock的性能
 */
public class ReadLockThreadTest {
    final static CountDownLatch countDownLatch = new CountDownLatch(200);
    public static void main(String[] args) throws InterruptedException {
        ReadWriteLock_RW readWriteLockRw = new ReadWriteLock_RW();
        ReadLockThread readThread = new ReadLockThread(readWriteLockRw, countDownLatch);
        long time = System.currentTimeMillis();
        for(int i = 0; i < 200; i++){
            Thread t = new Thread(readThread);
            t.start();
        }
        countDownLatch.await();  //一定要等到countDown()方法执行完毕后才使用,将主线程挂起
        System.out.println(System.currentTimeMillis() - time);
    }
}

结果

26

结论:使用读写锁来对数据进行读取,效率远远高于重入锁。

五)、使用重入锁进行写操作的性能比较

重入锁写操作线程:ReentrantLockWriteThread

public class ReentrantLockWriteThread implements Runnable {
    private CountDownLatch countDownLatch;
    private ReentrantLock_RW reentrantLockRw;

    public ReentrantLockWriteThread(ReentrantLock_RW reentrantLockRw, CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
        this.reentrantLockRw = reentrantLockRw;
    }

    @Override
    public void run() {
        try {
            reentrantLockRw.handleWrite(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //线程执行完,线程数减一
            countDownLatch.countDown();
        }
    }
}

写线程效率测试:ReentrantLockWriteThreadTest

public class ReentrantLockWriteThreadTest {
    final static CountDownLatch countDownLatch =  new CountDownLatch(200);
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock_RW reentrantLockRw = new ReentrantLock_RW();
        long start = System.currentTimeMillis();
        for(int i = 0; i < 200; i++){
            Thread t = new Thread(new ReentrantLockWriteThread(reentrantLockRw, countDownLatch));
            t.start();
        }
        countDownLatch.await();
        System.out.println(System.currentTimeMillis() - start);
    }
}

结果:

408

六)、使用读写锁进行写操作性能比较

读写锁写操作线程: WriteLockThread

public class WriteLockThread implements Runnable{
    private ReadWriteLock_RW readWriteLockrw;
    private CountDownLatch countDownLatch;
    public WriteLockThread(ReadWriteLock_RW readWriteLockrw, CountDownLatch countDownLatch) {
        this.readWriteLockrw = readWriteLockrw;
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            readWriteLockrw.handleWrite(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //线程执行完将该线程挂起
            countDownLatch.countDown();
        }
    }
}

写线程效率测试:WriteLockThreadTest

public class WriteLockThread implements Runnable{
    private ReadWriteLock_RW readWriteLockrw;
    private CountDownLatch countDownLatch;
    public WriteLockThread(ReadWriteLock_RW readWriteLockrw, CountDownLatch countDownLatch) {
        this.readWriteLockrw = readWriteLockrw;
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            readWriteLockrw.handleWrite(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //线程执行完将该线程挂起
            countDownLatch.countDown();
        }
    }
}

结果:

398

结论:使用读写锁和重入锁进行写操作的速率大致相同。

​ 在读多写少的场合,使用读写锁可以分离读操作和写操作,使所有读操作间

​ 真正的并行。

使用场景:当线程使用读写操作共享数据时,使用读写锁,可以减少读线程的等待

​ 时间提高系统的并发能力。

01-07 19:17