先一步一步来。
首先就是明白读写锁的功能:
读锁和读锁------不排斥
写锁和写锁------排斥
读锁和写锁------排斥
我们先定义一些属性:
//读写互斥,读读不互斥,写写互斥
private AtomicStampedReference<Thread> reference=new AtomicStampedReference<>(null,1);
private AtomicStampedReference<Thread> id=new AtomicStampedReference<>(null,1);
读锁和读锁------不排斥:
所以在写的时候,读锁的判定条件应该是二个或二个以上,这样保证,第一个线程通过条件能进来的同时,其他线程也能通过别的条件进来。
public void readLock(){
Thread thread=Thread.currentThread();
while ((!id.compareAndSet(null,null,id.getStamp(),id.getStamp()+1))
&&(!reference.compareAndSet(null,thread, 1,1))){
//循环堵塞
}
}
可以看到,这里的while判定条件我用了&&符号,保证了读锁的并发性。
id.compareAndSet(null,null,id.getStamp(),id.getStamp()+1) 他只有值为null的时候才为true
reference.compareAndSet(null,thread, 1,1) 他只有值为null的时候才会为true
扫描二维码关注公众号,回复: 16670291 查看本文章
写锁和写锁------排斥
读锁和写锁------排斥
public void writeLock(){
Thread thread=Thread.currentThread();
while (!reference.compareAndSet(null,thread,reference.getStamp(),1)
||!id.compareAndSet(null,thread,1,1)){
//循环堵塞
}
}
可以看到,这里的while判定条件我用了||符号,保证了写锁的互斥性。
id.compareAndSet(null,null,id.getStamp(),id.getStamp()+1) 他只有值为null的时候才为true
reference.compareAndSet(null,thread, 1,1) 注意这里:他只有值为null,并且他的版本号为1的时候才为true
我们可以看到一旦读锁(或者写锁)先执行了,那么其他线程的写锁(或者读锁)就会陷入死循环。
那么读锁(或者写锁)执行完了,该怎么开始执行写锁(或者读锁)呢?
那么我们就可以推断:
如果读锁先执行,需要给写锁放行的是:
id 的版本号为1,且reference的值为null
public void unReadLock(){
reference.compareAndSet(reference.getReference(),null,reference.getStamp(),1);
id.compareAndSet(reference.getReference(),null,id.getStamp(),id.getStamp()-1);
}
id.compareAndSet(null,null,id.getStamp(),id.getStamp()-1);
请注意这里,因为读锁是并发的,所以我们希望执行了几个读锁就要解开几个读锁,写锁才能执行。
如果写锁先执行,需要给写锁和读锁都放行:
id 的版本号为1,且reference的值不为null
public void unWriteLock(){
reference.compareAndSet(reference.getReference(),null,1,1);
id.compareAndSet(id.getReference(),null,1,1);
}
id.compareAndSet(id.getReference(),null,1,1);
写锁时互斥的,所以直接把他变成初始值就行了。
好,上面写完后,我们就实现了一个读写锁,读和写是互斥的,所以他是一种悲观锁。
悲观锁完成之后,我们希望还要加一个乐观锁。
首先明白乐观锁并不是一种锁,所以它不需要解锁,还要明白他的存在是解决读锁和写锁互斥的问题。
比方说,我们一直在读,那么可能负责写的这个线程就一直没有机会执行。这是非常不好的事情。
既然,乐观锁不和写锁互斥,我们该怎么保证数据安全呢?
答案是判断,如果写操作发生,我们就加上读锁,如果写没有发生,就不加读锁。
开始:先加一些属性:
private AtomicInteger ifWrite=new AtomicInteger(0);
private volatile int stamp;
ifWrite 用于判断写锁有没有发生
stamp 用于及时更新ifWrite的状态
写锁一执行,我们就改变 ifWrite
public void writeLock(){
Thread thread=Thread.currentThread();
while (!reference.compareAndSet(null,thread,reference.getStamp(),1)
||!id.compareAndSet(null,thread,1,1)){
//循环堵塞
}
ifWrite.compareAndSet(ifWrite.get(),99);
}
只要写锁执行了,我们就弃用乐观锁使用读锁,在读锁关的时候,再把状态该回去。
public void unReadLock(){
reference.compareAndSet(reference.getReference(),null,reference.getStamp(),1);
id.compareAndSet(reference.getReference(),null,id.getStamp(),id.getStamp()-1);
ifWrite.compareAndSet(ifWrite.get(),0);
}
那么好了,我们的乐观锁只需要保证获取到ifWrite赋值给stamp就行了。
public void OptimisticReadLock(){
this.stamp=ifWrite.get();
}
public Boolean validate(){
if(this.stamp==99)
return true;
return false;
}
好了,完整代码就是:
public class XjggLock {
private AtomicStampedReference<Thread> reference=new AtomicStampedReference<>(null,1);
private AtomicStampedReference<Thread> id=new AtomicStampedReference<>(null,1);
private AtomicInteger ifWrite=new AtomicInteger(0);
private volatile int stamp;
public void readLock(){
Thread thread=Thread.currentThread();
while ((!id.compareAndSet(null,null,id.getStamp(),id.getStamp()+1))
&&(!reference.compareAndSet(null,thread, 1,1))){
//循环堵塞
}
}
public void unReadLock(){
reference.compareAndSet(reference.getReference(),null,reference.getStamp(),1);
id.compareAndSet(reference.getReference(),null,id.getStamp(),id.getStamp()-1);
ifWrite.compareAndSet(ifWrite.get(),0);
}
public void writeLock(){
Thread thread=Thread.currentThread();
while (!reference.compareAndSet(null,thread,reference.getStamp(),1)
||!id.compareAndSet(null,thread,1,1)){
//循环堵塞
}
ifWrite.compareAndSet(ifWrite.get(),99);
}
public void unWriteLock(){
reference.compareAndSet(reference.getReference(),null,1,1);
id.compareAndSet(id.getReference(),null,1,1);
}
public void OptimisticReadLock(){
this.stamp=ifWrite.get();
}
public Boolean validate(){
if(this.stamp==99)
return true;
return false;
}
}
代码很少,很简单。
我们来测试一下:
读并发:
class Test{
private static XjggLock xjggLock=new XjggLock();
private static int num;
static void read(){
xjggLock.readLock();
try {
int n=num;
System.out.println(Thread.currentThread().getName()+"===>读执行");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>读结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unReadLock();
}
}
public static void main(String[] args) {
//测试读能并发
for (int i = 0; i < 3; i++) {
new Thread(()->{
read();
}).start();
}
}
}
结果:可以看到没有问题
Thread-1===>读执行
Thread-2===>读执行
Thread-0===>读执行
Thread-0===>读结束
Thread-2===>读结束
Thread-1===>读结束
写互斥:
class Test{
private static XjggLock xjggLock=new XjggLock();
private static int num;
static void write(){
xjggLock.writeLock();
try {
num+=99;
System.out.println(Thread.currentThread().getName()+"===>写执行");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>写结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unWriteLock();
}
}
public static void main(String[] args) {
//测试写不能并发
for (int i = 0; i < 3; i++) {
new Thread(()->{
write();
}).start();
}
}
}
结果:可以看到没有问题
Thread-0===>写执行
Thread-0===>写结束
Thread-2===>写执行
Thread-2===>写结束
Thread-1===>写执行
Thread-1===>写结束
读写互斥:
class Test{
private static XjggLock xjggLock=new XjggLock();
private static int num;
static void read(){
xjggLock.readLock();
try {
int n=num;
System.out.println(Thread.currentThread().getName()+"===>读执行");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>读结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unReadLock();
}
}
static void write(){
xjggLock.writeLock();
try {
num+=99;
System.out.println(Thread.currentThread().getName()+"===>写执行");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>写结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unWriteLock();
}
}
public static void main(String[] args) {
//测试读写不能并发
for (int i = 0; i < 2; i++) {
new Thread(()->{
read();
}).start();
}
for (int i = 0; i < 2; i++) {
new Thread(()->{
write();
}).start();
}
for (int i = 0; i < 2; i++) {
new Thread(()->{
read();
}).start();
}
}
}
结果:可以看到没有问题
Thread-0===>读执行
Thread-4===>读执行
Thread-1===>读执行
Thread-5===>读执行
Thread-1===>读结束
Thread-0===>读结束
Thread-4===>读结束
Thread-5===>读结束
Thread-3===>写执行
Thread-3===>写结束
Thread-2===>写执行
Thread-2===>写结束
再测试测试乐观锁:
class Test{
private static XjggLock xjggLock=new XjggLock();
private static int num;
static void read(){
xjggLock.OptimisticReadLock();//加一个乐观锁
if(xjggLock.validate()){
xjggLock.readLock();
System.out.println("当前写操作已发生,为了安全,我们加上读锁,此时写锁不可以进来。");
try {
int n=num;
System.out.println(Thread.currentThread().getName()+"===>读执行,写锁不可以进来");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>读结束,写锁不可以进来");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unReadLock();
}
}else{
int n=num;
System.out.println("当前没有发生写的操作,我们可以直接取值,此时写锁可以进来。");
System.out.println(Thread.currentThread().getName()+"===>读执行,写锁可以进来");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===>读结束,写锁可以进来");
}
}
static void write(){
xjggLock.writeLock();
try {
num+=99;
System.out.println(Thread.currentThread().getName()+"===>写执行");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>写结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unWriteLock();
}
}
public static void main(String[] args) {
//测试乐观锁
for (int i = 0; i < 2; i++) {
new Thread(()->{
read();
}).start();
}
for (int i = 0; i < 2; i++) {
new Thread(()->{
write();
}).start();
}
}
}
结果:可以看到没有问题
当前没有发生写的操作,我们可以直接取值,此时写锁可以进来。
Thread-1===>读执行,写锁可以进来
Thread-3===>写执行
Thread-1===>读结束,写锁可以进来
Thread-3===>写结束
当前写操作已发生,为了安全,我们加上读锁,此时写锁不可以进来。
Thread-0===>读执行,写锁不可以进来
Thread-0===>读结束,写锁不可以进来
Thread-2===>写执行
Thread-2===>写结束
总结一下就是,读写锁,可以提高读的效率,还保证了数据的安全性,而加入乐观锁保证了读的时候也可以写,进一步提升了执行效率,再配合读锁,也保证了数据的安全性。
当然就这几行代码想把读写锁写的很完美是不现实的,可能它会存在一些问题,虽然我还没测试出什么问题,但是它应该是不可信的!!!
像这么好用的锁,JDK肯定是会有的。JDK8开始新增锁(java.util.concurrent.locks.StampedLock)所以在业务中我们直接用这个类就行了。
希望对你们有帮助!