Synchronized读写锁(三)

在这里插入图片描述

读写锁简介

ReentrantReadWriteLock
被定义为一个资源能够被多个读线程访问,或者被多个写线程访问,但是不能同时存在读写线程

读写锁演变

无锁->独占锁->读写锁->邮戳锁

无锁

无约束、多线程情况数据错乱

独占锁

Synchronized、Lock接口,数据有序且一致性ok,多线程过来,不管读还是写,每次都是一个线程执行完成,并发性不高
在这里插入图片描述

读写锁

ReadWriteLock、ReentrantReadWriteLock,读写互斥,读读共享,一个资源可以被多个读操作访问或一个写操作访问,但是两者不能同时进行,读多写少场景下,读写锁性能较高,但是会有写锁饥饿和锁降级问题

下面的案例是写操作下不可被打断,但是读操作下可以共享读,各线程直接交叉进行
在这里插入图片描述

写饥饿

在读操作未完成的时候,写操作无法进入,大量的写操作堆积,造成的现象

场景演示:在上一个案例中稍作调整,将读操作暂停2000毫秒,同时在main方法里读线程后面暂停1秒,再执行写线程操作,就会发现,在读操作未完全结束时,写操作无法获取锁
在这里插入图片描述

锁降级

如果同一个线程持有了写锁,在没有释放写锁的情况下,可以继续获得读锁,这就是写锁的降级,降级成了读锁,但是不允许从读锁升级成写锁,锁升级目的是保证数据的可见性

写锁降级读锁演示:
在这里插入图片描述

读锁未释放,写锁无法获取锁演示:
在这里插入图片描述
即ReentrantReadWriteLock,读的过程中不允许写,只有等到读完成后才能写,这相等于悲观的读锁策略

邮戳锁

StampedLock是JDK1.8新增的一种读写锁,也是对了JDK1.5中的读写锁ReentrantReadWriteLock的优化。

也可以叫做票据锁,有个stamp戳记,long类型,代表锁的状态,当stamp返回零时,表示线程获取锁失败,并且当释放锁或者转换锁的时候,都要传入stamp的最初值

一般ReentrantReadWriteLock(true)可以缓解锁饥饿问题,使用公平策略可以一定程度上缓解这个问题,但是公平策略是以牺牲系统吞吐量为代价

而StampedLock不会以吞吐量为代价,采用乐观获取锁之后,其他线程不会被堵塞,对读锁进行了优化,但也是如此,在使用StampedLock乐观获取锁之后,需要对结果进行校验

特点

  • 所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为零表示获取失败,其余都表示成功;
  • 所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致:
  • StampedLock是不可重入的,危险(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)
  • StampedLock有三种访问模式
    ①Reading(读模式悲观):功能和ReentrantReadWriteLock的读锁类似
    ②Writing(写模式):功能和ReentrantRegdWriteLock的写锁类似
    ③Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式

案例演示

传统读写锁(悲观读)使用演示:

package com.example.demo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;

public class Demo {

    static int number = 37;
    static StampedLock stampedLock = new StampedLock();

    public void write() {
        long stamp = stampedLock.writeLock();
        System.out.println(Thread.currentThread().getName() + "\t 写线程准备修改");
        try {
            number = number + 10;
        } finally {
            stampedLock.unlockWrite(stamp);
        }
        System.out.println(Thread.currentThread().getName() + "\t 写线程结束修改");
    }

    //悲观读 读没有完成写锁无法获得锁
    public void read() {
        long stamp = stampedLock.readLock();
        System.out.println(Thread.currentThread().getName() + "\t come in readLock,4 seconds continue");
        for (int i = 0; i < 4; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "\t 正在读取中。。。。");
        }
        try {
            int result = number;
            System.out.println(Thread.currentThread().getName() + "\t 获得成员变量值result:" + result);
            System.out.println("写线程没有修改成功,读锁时候写锁无法介入,传统的读写锁互斥");
        } finally {
            stampedLock.unlockRead(stamp);
        }


    }

    public static void main(String[] args) {
        Demo demo = new Demo();

        new Thread(() -> {
            demo.read();
        }, "readThread").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t com in ");
            demo.write();
        }, "writeThread").start();

        try {
            TimeUnit.SECONDS.sleep(8);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(Thread.currentThread().getName() + "\t number :" + number);
    }
}


在这里插入图片描述

乐观读没有通过案例演示:

package com.example.demo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;

public class Demo {

    static int number = 37;
    static StampedLock stampedLock = new StampedLock();

    public void write() {
        long stamp = stampedLock.writeLock();
        System.out.println(Thread.currentThread().getName() + "\t 写线程准备修改");
        try {
            number = number + 10;
        } finally {
            stampedLock.unlockWrite(stamp);
        }
        System.out.println(Thread.currentThread().getName() + "\t 写线程结束修改");
    }

    //悲观读 读没有完成写锁无法获得锁
    public void read() {
        long stamp = stampedLock.readLock();
        System.out.println(Thread.currentThread().getName() + "\t come in readLock,4 seconds continue");
        for (int i = 0; i < 4; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "\t 正在读取中。。。。");
        }
        try {
            int result = number;
            System.out.println(Thread.currentThread().getName() + "\t 获得成员变量值result:" + result);
            System.out.println("写线程没有修改成功,读锁时候写锁无法介入,传统的读写锁互斥");
        } finally {
            stampedLock.unlockRead(stamp);
        }

    }

    //乐观读 读的过程中允许写锁进入
    public void tryOptimisticRead() {
        long stamp = stampedLock.tryOptimisticRead();
        int result = number;
        System.out.println("4秒前stampLock,validate方法值(true无修改,false有修改)" + stampedLock.validate(stamp));
        for (int i = 0; i < 4; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "\t 正在读取。。。" + i + "秒后validate方法值(true无修改,false有修改)\t" + stampedLock.validate(stamp));
            if (!stampedLock.validate(stamp)) {
                System.out.println("有人修改过。。。。有写操作");
                stamp = stampedLock.readLock();

                try {
                    System.out.println("从乐观读 升级为 悲观读");
                    result = number;
                    System.out.println("悲观读后的result:" + result);
                } finally {
                    stampedLock.unlockRead(stamp);
                }

            }
            System.out.println(Thread.currentThread().getName() + "\t finally value:" + result);

        }
    }

    public static void main(String[] args) {
        Demo demo = new Demo();

        /* 传统版本 悲观读
        new Thread(() -> {
            demo.read();
        }, "readThread").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t com in ");
            demo.write();
        }, "writeThread").start();

        try {
            TimeUnit.SECONDS.sleep(8);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(Thread.currentThread().getName() + "\t number :" + number);
         */


        //乐观读

        new Thread(() -> {
            demo.tryOptimisticRead();
        }, "readThread").start();

        //暂停2秒 写可以介入
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t com in ");
            demo.write();
        }, "writeThread").start();

    }
}

在这里插入图片描述

乐观读通过案例演示:

package com.example.demo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;

public class Demo {

    static int number = 37;
    static StampedLock stampedLock = new StampedLock();

    public void write() {
        long stamp = stampedLock.writeLock();
        System.out.println(Thread.currentThread().getName() + "\t 写线程准备修改");
        try {
            number = number + 10;
        } finally {
            stampedLock.unlockWrite(stamp);
        }
        System.out.println(Thread.currentThread().getName() + "\t 写线程结束修改");
    }

    //悲观读 读没有完成写锁无法获得锁
    public void read() {
        long stamp = stampedLock.readLock();
        System.out.println(Thread.currentThread().getName() + "\t come in readLock,4 seconds continue");
        for (int i = 0; i < 4; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "\t 正在读取中。。。。");
        }
        try {
            int result = number;
            System.out.println(Thread.currentThread().getName() + "\t 获得成员变量值result:" + result);
            System.out.println("写线程没有修改成功,读锁时候写锁无法介入,传统的读写锁互斥");
        } finally {
            stampedLock.unlockRead(stamp);
        }

    }

    //乐观读 读的过程中允许写锁进入
    public void tryOptimisticRead() {
        long stamp = stampedLock.tryOptimisticRead();
        int result = number;
        System.out.println("4秒前stampLock,validate方法值(true无修改,false有修改)" + stampedLock.validate(stamp));
        for (int i = 0; i < 4; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "\t 正在读取。。。" + i + "秒后validate方法值(true无修改,false有修改)\t" + stampedLock.validate(stamp));
            if (!stampedLock.validate(stamp)) {
                System.out.println("有人修改过。。。。有写操作");
                stamp = stampedLock.readLock();

                try {
                    System.out.println("从乐观读 升级为 悲观读");
                    result = number;
                    System.out.println("悲观读后的result:" + result);
                } finally {
                    stampedLock.unlockRead(stamp);
                }

            }
            System.out.println(Thread.currentThread().getName() + "\t finally value:" + result);

        }
    }

    public static void main(String[] args) {
        Demo demo = new Demo();

        /* 传统版本 悲观读
        new Thread(() -> {
            demo.read();
        }, "readThread").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t com in ");
            demo.write();
        }, "writeThread").start();

        try {
            TimeUnit.SECONDS.sleep(8);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(Thread.currentThread().getName() + "\t number :" + number);
         */


        //乐观读

        new Thread(() -> {
            demo.tryOptimisticRead();
        }, "readThread").start();

        //暂停6秒 写可以介入 
        try {
            TimeUnit.SECONDS.sleep(6);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t com in ");
            demo.write();
        }, "writeThread").start();

    }
}

在这里插入图片描述

StampedLock缺点

  • StampedLock不支持重入,没有Re开头
  • StampedLock的悲观读锁和写锁都不支持条件变量(Condiion),这个也需要注意。
  • 使用 StampedLock一定不要调用中断操作,即不要调用interrupt()方法(会影响性能和产生一些意外情况)

相关文献

ReentrantReadWriteLock:https://www.runoob.com/manual/jdk11api/java.base/java/util/concurrent/locks/ReentrantReadWriteLock.html
StampedLock:https://www.runoob.com/manual/jdk11api/java.base/java/util/concurrent/locks/StampedLock.html

就先说到这 \color{#008B8B}{ 就先说到这} 就先说到这
在下 A p o l l o \color{#008B8B}{在下Apollo} 在下Apollo
一个爱分享 J a v a 、生活的小人物, \color{#008B8B}{一个爱分享Java、生活的小人物,} 一个爱分享Java、生活的小人物,
咱们来日方长,有缘江湖再见,告辞! \color{#008B8B}{咱们来日方长,有缘江湖再见,告辞!} 咱们来日方长,有缘江湖再见,告辞!

在这里插入图片描述

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值