前面我们讲解了Lock的使用,下面我们来讲解一下ReadWriteLock锁的使用,顾明思义,读写锁在读的时候,上读锁,在写的时候,上写锁,这样就很巧妙的解决synchronized的一个性能问题:读与读之间互斥。
ReadWriteLock也是一个接口,原型如下:
public interface ReadWriteLock { Lock readLock(); Lock writeLock(); }
该接口只有两个方法,读锁和写锁。也就是说,我们在写文件的时候,可以将读和写分开,分成2个锁来分配给线程,从而可以做到读和读互不影响,读和写互斥,写和写互斥,提高读写文件的效率。该接口也有一个实现类ReentrantReadWriteLock,下面我们就来学习下这个类。
我们先看一下,多线程同时读取文件时,用synchronized实现的效果,代码如下:
public class ReadAndWriteLock { public synchronized void get(Thread thread) { System.out.println("start time:"+System.currentTimeMillis()); for(int i=0; i<5; i++){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(thread.getName() + ":正在进行读操作……"); } System.out.println(thread.getName() + ":读操作完毕!"); System.out.println("end time:"+System.currentTimeMillis()); } public static void main(String[] args) { final ReadAndWriteLock lock = new ReadAndWriteLock(); new Thread(new Runnable() { @Override public void run() { lock.get(Thread.currentThread()); } }).start(); new Thread(new Runnable() { @Override public void run() { lock.get(Thread.currentThread()); } }).start(); } }
测试结果如下:
start time:1442459467623 Thread-0:正在进行读操作…… Thread-0:正在进行读操作…… Thread-0:正在进行读操作…… Thread-0:正在进行读操作…… Thread-0:正在进行读操作…… Thread-0:读操作完毕! end time:1442459467723 start time:1442459467723 Thread-1:正在进行读操作…… Thread-1:正在进行读操作…… Thread-1:正在进行读操作…… Thread-1:正在进行读操作…… Thread-1:正在进行读操作…… Thread-1:读操作完毕! end time:1442459467823
整个过程耗时200ms
我们可以看到,及时是在读取文件,在加了synchronized关键字之后,读与读之间,也是互斥的,也就是说,必须等待Thread-0读完之后,才会轮到Thread-1线程读,而无法做到同时读文件,这种情况在大量线程同时都需要读文件的时候,读写锁的效率,明显要高于synchronized关键字的实现。下面我们来测试一下,代码如下:
public class ReadAndWriteLock { ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public void get(Thread thread) { lock.readLock().lock(); try{ System.out.println("start time:"+System.currentTimeMillis()); for(int i=0; i<5; i++){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(thread.getName() + ":正在进行读操作……"); } System.out.println(thread.getName() + ":读操作完毕!"); System.out.println("end time:"+System.currentTimeMillis()); }finally{ lock.readLock().unlock(); } } public static void main(String[] args) { final ReadAndWriteLock lock = new ReadAndWriteLock(); new Thread(new Runnable() { @Override public void run() { lock.get(Thread.currentThread()); } }).start(); new Thread(new Runnable() { @Override public void run() { lock.get(Thread.currentThread()); } }).start(); } }
测试结果如下:
start time:1442460030593 start time:1442460030593 Thread-1:正在进行读操作…… Thread-0:正在进行读操作…… Thread-1:正在进行读操作…… Thread-0:正在进行读操作…… Thread-1:正在进行读操作…… Thread-0:正在进行读操作…… Thread-1:正在进行读操作…… Thread-0:正在进行读操作…… Thread-1:正在进行读操作…… Thread-0:正在进行读操作…… Thread-0:读操作完毕! Thread-1:读操作完毕! end time:1442460030693 end time:1442460030693
整个过程耗时:100ms
从测试结果来看,Thread-0和Thread-1是在同时读取文件。
通过两次实验的对比,我们可以看出来,读写锁的效率明显高于synchronized关键字
不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。读锁和写锁是互斥的。
下面我们来验证下读写锁的互斥关系,代码如下:
public class ReadAndWriteLock { ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public static void main(String[] args) { final ReadAndWriteLock lock = new ReadAndWriteLock(); // 建N个线程,同时读 ExecutorService service = Executors.newCachedThreadPool(); service.execute(new Runnable() { @Override public void run() { lock.readFile(Thread.currentThread()); } }); // 建N个线程,同时写 ExecutorService service1 = Executors.newCachedThreadPool(); service1.execute(new Runnable() { @Override public void run() { lock.writeFile(Thread.currentThread()); } }); } // 读操作 public void readFile(Thread thread){ lock.readLock().lock(); boolean readLock = lock.isWriteLocked(); if(!readLock){ System.out.println("当前为读锁!"); } try{ for(int i=0; i<5; i++){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(thread.getName() + ":正在进行读操作……"); } System.out.println(thread.getName() + ":读操作完毕!"); }finally{ System.out.println("释放读锁!"); lock.readLock().unlock(); } } // 写操作 public void writeFile(Thread thread){ lock.writeLock().lock(); boolean writeLock = lock.isWriteLocked(); if(writeLock){ System.out.println("当前为写锁!"); } try{ for(int i=0; i<5; i++){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(thread.getName() + ":正在进行写操作……"); } System.out.println(thread.getName() + ":写操作完毕!"); }finally{ System.out.println("释放写锁!"); lock.writeLock().unlock(); } } }
测试结果如下:
// 读锁和读锁测试结果: 当前为读锁! 当前为读锁! pool-2-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-2-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-2-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-2-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-2-thread-1:正在进行读操作…… pool-1-thread-1:读操作完毕! pool-2-thread-1:读操作完毕! 释放读锁! 释放读锁! // 测试结果不互斥 // 读锁和写锁,测试结果如下: 当前为读锁! pool-1-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-1-thread-1:正在进行读操作…… pool-1-thread-1:读操作完毕! 释放读锁! 当前为写锁! pool-2-thread-1:正在进行写操作…… pool-2-thread-1:正在进行写操作…… pool-2-thread-1:正在进行写操作…… pool-2-thread-1:正在进行写操作…… pool-2-thread-1:正在进行写操作…… pool-2-thread-1:写操作完毕! 释放写锁! // 测试结果互斥 // 写锁和写锁,测试结果如下: 当前为写锁! pool-1-thread-1:正在进行写操作…… pool-1-thread-1:正在进行写操作…… pool-1-thread-1:正在进行写操作…… pool-1-thread-1:正在进行写操作…… pool-1-thread-1:正在进行写操作…… pool-1-thread-1:写操作完毕! 释放写锁! 当前为写锁! pool-2-thread-1:正在进行写操作…… pool-2-thread-1:正在进行写操作…… pool-2-thread-1:正在进行写操作…… pool-2-thread-1:正在进行写操作…… pool-2-thread-1:正在进行写操作…… pool-2-thread-1:写操作完毕! 释放写锁! // 测试结果互斥
关于锁的内容我们就讲到这里,下面对锁的相关概念做一个介绍:
1.可重入(Reentrant)锁
如果锁具备可重入性,则称作为可重入锁。像synchronized和 ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一 个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法 method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
看下面这段代码就明白了:
classMyClass { public synchronized void method1() { method2(); } public synchronized void method2() { } }
上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程 A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请 锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。 而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。
2.可中断锁
可中断锁:顾名思义,就是可以相应中断的锁。
在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
3.公平锁
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。设置方法如下:ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
4、读写锁
前面已经介绍,这里不做赘述
——————— 本文来自 牛奋lch 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/liuchuanhong1/article/details/53539341?utm_source=copy