双峰做网站/爱站关键词挖掘工具
文章目录
- 前言
- 简介
- 案例
- 无锁情况下
- 使用 ReentrantLock 加锁
- ReentrantReadWriteLock 读写锁
前言
代码中,针对加锁的区块,多线程执行调用时,会根据先获取到锁先执行,其他线程必须等待其成功释放锁后才能继续使用资源。
整体而言虽然保证了数据的完整性,但是对于效率来说,会有所降低。针对此问题,在JUC包下存在一个读写锁类ReadWriteLock
。
简介
ReadWriteLock
在java中是一个接口
。有且仅有一个官方定义的子类java.util.concurrent.locks.ReentrantReadWriteLock
。
从JDK 1.8
开发文档中的介绍得知,针对读锁
操作,允许多个线程同时执行;针对写操作,只允许线程依次执行。
下面看一个栗子。
案例
无锁情况下
多线程操作一个缓存类,同时
执行添加
、获取
操作,代码案例如下所示:
package demo5_2;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;public class NoLockTest {public static void main(String[] args) throws InterruptedException {// 缓存只能单例,让多个线程同时操作一个资源,才会有并发安全问题MyNoLockCache myNoLockCache = new MyNoLockCache();// 1、开启多个线程进行写操作for (int i = 1; i <= 20 ; i++) {// 临时变量final int temp = i;new Thread(()->{myNoLockCache.set(String.valueOf(temp),"66666");},String.valueOf(i)).start();}TimeUnit.SECONDS.sleep(4);// 2、多个线程读操作for (int i = 1; i <= 20 ; i++) {// 临时变量final int temp = i;new Thread(()->{myNoLockCache.get(String.valueOf(temp));},String.valueOf(i)).start();}}/*** 没有锁的时候,设置总会被插队,导致日志打印不整齐;<br />*/
}
/*** 自定义缓存类,实现数据的 保存 和 获取操作*/
class MyNoLockCache{// 由于需要保存数据,此处采取集合的方式存储private Map<String,Object> maps = new HashMap<>();public void set(String key,Object value){System.out.println("当前为 "+key+" 进行数据存储");maps.put(key,value);System.out.println("当前为 "+key+" 数据保存 OK");}public void get(String key){System.out.println(key+" 获取数据");maps.get(key);System.out.println(key+" 获取数据 OK");}
}
其中运行日志如下所示:
没有加锁的情况下,添加数据至缓存中,总会出现其他线程插队的现象。
使用 ReentrantLock 加锁
为了保证写入数据操作执行时,其他线程不会对其进行干扰操作,此时需要在set
和get
方法中添加锁,保证顺序执行。
package demo5_2;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockTest {public static void main(String[] args) throws InterruptedException {// 缓存只能单例,让多个线程同时操作一个资源,才会有并发安全问题MyLockCache myLockCache = new MyLockCache();// 1、开启多个线程进行写操作for (int i = 1; i <= 30 ; i++) {// 临时变量final int temp = i;new Thread(()->{myLockCache.set(String.valueOf(temp),"66666");},String.valueOf(i)).start();}// 这里的时间只是为了测试,时间越长只是将读和写分开。// 测试读、写操作交替执行导致的问题现象可以频闭此延迟!TimeUnit.SECONDS.sleep(4);// 2、多个线程读操作for (int i = 1; i <= 30 ; i++) {// 临时变量final int temp = i;new Thread(()->{myLockCache.get(String.valueOf(temp));},String.valueOf(i)).start();}}
}/*** 自定义缓存类,实现数据的 保存 和 获取操作*/
class MyLockCache{// 由于需要保存数据,此处采取集合的方式存储private Map<String,Object> maps = new HashMap<>();// 生成对象资源的时候 就创建一把锁private Lock lock = new ReentrantLock();public void set(String key,Object value){this.lock.lock();try {System.out.println("当前为 "+key+" 进行数据存储");maps.put(key,value);System.out.println("当前为 "+key+" 数据保存 OK");}finally {this.lock.unlock();}}public void get(String key){// 当读操作不加锁,会在写操作中插队!this.lock.lock();try {System.out.println(key+" 获取数据");Object o = maps.get(key);System.out.println(key+" 获取数据 OK = "+o);}finally {this.lock.unlock();}}
}
执行后,控制台日志打印信息如下所示:
读写操作都能打印添加(获取)数据和添加(获取)ok,
但是,读操作也进行了加锁,当只是读操作执行,此时并不需要进行加锁,加了锁反而影响了执行效率。
[疑问:]
到这里可能有人会问:
既然读操作加了锁影响了效率,那读操作就不加锁嘛。
但是结合之前解释的八锁效应
,读操作不加锁和写操作加锁,那么读操作会插队至写操作!
ReentrantReadWriteLock 读写锁
为了保证数据写入顺序性,必须要求在以下状态下加锁和无锁:
- 读 - 读 操作:
读操作,只是从Map集合中获取数据,有就返回数据信息,无数据则返回null。并无并发问题,所以不需要加锁
- 读 - 写 操作:
考虑到多个线程同时执行,读操作无锁,但写操作必须加锁保证数据操作安全行;结合来看就必须保证同时进行读写
操作需要加锁
。 - 写 - 写 操作
写和写操作之间,必须保证每个线程操作执行时的安全问题,需要加锁
。
使用 ReentrantReadWriteLock
就能完美解决上述问题。
看下面栗子:
package demo5_2;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockTest {public static void main(String[] args) throws InterruptedException {// 缓存只能单例,让多个线程同时操作一个资源,才会有并发安全问题MyRWLock myRWLock = new MyRWLock();// 1、开启多个线程进行写操作for (int i = 1; i <= 10 ; i++) {// 临时变量final int temp = i;new Thread(()->{myRWLock.set(String.valueOf(temp),"66666");},String.valueOf(i)).start();}// 时间的长短 取决于测试 写-读 还是 单读,可以分别设定不同时间测试效果TimeUnit.SECONDS.sleep(2);// 2、多个线程读操作for (int i = 1; i <= 10 ; i++) {// 临时变量final int temp = i;new Thread(()->{myRWLock.get(String.valueOf(temp));},String.valueOf(i)).start();}}
}
class MyRWLock{// 由于需要保存数据,此处采取集合的方式存储private Map<String,Object> maps = new HashMap<>();// ReentrantReadWriteLock 创建读写锁private ReadWriteLock readWriteLock = new ReentrantReadWriteLock() ;public void set(String key,Object value){// 创建写锁this.readWriteLock.writeLock().lock();try {System.out.println("当前为 "+key+" 进行数据存储");maps.put(key,value);System.out.println("当前为 "+key+" 数据保存 OK");}finally {this.readWriteLock.writeLock().unlock();}}public void get(String key){// 创建读锁this.readWriteLock.readLock().lock();try {System.out.println(key+" 获取数据");maps.get(key);System.out.println(key+" 获取数据 OK");}finally {this.readWriteLock.readLock().unlock();}}
}
[发现:]
1、当设置延迟时间
保证写操作和读操作分开执行时。日志如下:
写操作会有顺序(写和写ok)一一对应!
读操作则不会对应!
读写锁,写-写会加锁、读-读不会有锁。
2、取消延迟,测试写-读
即使出现写操作中插队有读操作,但日志
操作执行
和执行ok
是一一对应
的,说明在写-读
操作中存在锁!