bilibili尚硅谷周阳老师JUC并发编程与源码分析课程笔记第十三章——ReentrantLock、ReentrantReadWriteLock、StampedLock讲解

ReentrantLock、ReentrantReadWriteLock、StampedLock讲解

本章路线总纲

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

关于锁的面试题

  • 你知道Java里面有那些锁?
  • 你说说你用过的锁,锁饥饿问题是什么?
  • 有没有比读写锁更快的锁?
  • StampedLock知道吗?(邮戳锁/票据锁)
  • ReentrantReadWriteLock有锁降级机制,你知道吗?

简单聊聊ReentrantReadWriteLock

类继承关系

在这里插入图片描述

是什么

读写锁说明

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

一体两面,读写互斥,读读共享;刀刃、刀背互斥(写的时候不可以读,读的时候不能写)

再说说演变

无锁无序->加锁->读写锁->邮戳锁

读写锁意义和特点

在这里插入图片描述

它只允许读读共存,而读写和写写依然是互斥的,大多实际场景是**”读/读“线程间不存在互斥关系**,只有”读/写“线程或者”写/写“线程间的操作是需要互斥的,因此引入了 ReentrantReadWriteLock

一个ReentrantReadWriteLock同时只能存在一个写锁但是可以存在多个读锁,但是不能同时存在写锁和读锁,也即一个资源可以被多个读操作访问,或一个写操作访问,但两者不能同时进行

只有在读多写少情景之下,读写锁才具有较高的性能体现

特点

可重入
读写兼顾
案例演示
ReentrantLock实现读写操作
package com.bilibili.juc.reentrantreadwritelock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockDemo {
    public static void main(String[] args) {
        MyResource myResource = new MyResource();

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                myResource.write(finalI + "", finalI + "");
            }, String.valueOf(i)).start();
        }

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                myResource.read(finalI + "");
            }, String.valueOf(i)).start();
        }
    }
}


// 资源类,模拟一个简单的缓存
class MyResource {
    Map<String, String> map = new HashMap<>();
    // ReentrantLock等价于synchronized,之前讲解过
    Lock lock = new ReentrantLock();

    public void write(String key, String value) {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t" + "正在写入");
            map.put(key, value);
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "完成写入");
        } finally {
            lock.unlock();
        }
    }

    public void read(String key) {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t" + "正在读取");
            String result = map.get(key);
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "完成读取" + "\t" + result);
        } finally {
            lock.unlock();
        }
    }
}

输出结果:
1	正在写入
1	完成写入
0	正在写入
0	完成写入
2	正在写入
2	完成写入
3	正在写入
3	完成写入
4	正在写入
4	完成写入
5	正在写入
5	完成写入
6	正在写入
6	完成写入
7	正在写入
7	完成写入
8	正在写入
8	完成写入
0	正在读取
0	完成读取	0
1	正在读取
1	完成读取	1
9	正在写入
9	完成写入
2	正在读取
2	完成读取	2
3	正在读取
3	完成读取	3
4	正在读取
4	完成读取	4
5	正在读取
5	完成读取	5
6	正在读取
6	完成读取	6
7	正在读取
7	完成读取	7
8	正在读取
8	完成读取	8
9	正在读取
9	完成读取	9
结论

使用ReentrantLock实现读写操作,任何一个线程正在读/写的时候不允许被打断

ReentrantReadWriteLock实现读写操作1
package com.bilibili.juc.reentrantreadwritelock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockDemo {
    public static void main(String[] args) {
        MyResource myResource = new MyResource();

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                myResource.write(finalI + "", finalI + "");
            }, String.valueOf(i)).start();
        }

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                myResource.read(finalI + "");
            }, String.valueOf(i)).start();
        }
    }
}


// 资源类,模拟一个简单的缓存
class MyResource {
    Map<String, String> map = new HashMap<>();
    // ReentrantReadWriteLock 一体两面,读写互斥,读读共享
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void write(String key, String value) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t" + "正在写入");
            map.put(key, value);
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "完成写入");
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void read(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t" + "正在读取");
            String result = map.get(key);
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "完成读取" + "\t" + result);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

输出结果:
1	正在写入
1	完成写入
0	正在写入
0	完成写入
4	正在写入
4	完成写入
2	正在写入
2	完成写入
5	正在写入
5	完成写入
3	正在写入
3	完成写入
6	正在写入
6	完成写入
6	正在读取
6	完成读取	6
8	正在写入
8	完成写入
9	正在写入
9	完成写入
0	正在读取
1	正在读取
2	正在读取
3	正在读取
1	完成读取	1
0	完成读取	0
2	完成读取	2
3	完成读取	3
7	正在写入
7	完成写入
4	正在读取
5	正在读取
7	正在读取
8	正在读取
9	正在读取
9	完成读取	9
5	完成读取	5
4	完成读取	4
7	完成读取	7
8	完成读取	8
结论

使用ReadWriteLock实现读写操作,任何一个线程写的时候不允许被打断,但是任何一个线程读的时候允许其它线程也进行读

ReentrantReadWriteLock实现读写操作2
package com.bilibili.juc.reentrantreadwritelock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockDemo {
    public static void main(String[] args) {
        MyResource myResource = new MyResource();

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> myResource.write(finalI + "", finalI + ""), String.valueOf(i)).start();
        }

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> myResource.read(finalI + ""), String.valueOf(i)).start();
        }

        // 暂停1秒钟
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < 3; i++) {
            int finalI = i;
            new Thread(() -> myResource.write(finalI + "", finalI + ""), "新写锁线程->" + i).start();
        }
    }
}


// 资源类,模拟一个简单的缓存
class MyResource {
    Map<String, String> map = new HashMap<>();
    // ReentrantReadWriteLock 一体两面,读写互斥,读读共享
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void write(String key, String value) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t" + "正在写入");
            map.put(key, value);
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "完成写入");
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void read(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t" + "正在读取");
            String result = map.get(key);
            // 暂停2000毫秒,演示读锁没有完成之前,写锁无法获取
            try {
                TimeUnit.MILLISECONDS.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "完成读取" + "\t" + result);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

输出结果:
1	正在写入
1	完成写入
2	正在写入
2	完成写入
0	正在写入
0	完成写入
3	正在写入
3	完成写入
6	正在写入
6	完成写入
7	正在写入
7	完成写入
4	正在写入
4	完成写入
5	正在写入
5	完成写入
8	正在写入
8	完成写入
9	正在写入
9	完成写入
0	正在读取
1	正在读取
2	正在读取
4	正在读取
5	正在读取
6	正在读取
3	正在读取
9	正在读取
8	正在读取
7	正在读取
9	完成读取	9
0	完成读取	0
1	完成读取	1
4	完成读取	4
7	完成读取	7
5	完成读取	5
8	完成读取	8
3	完成读取	3
6	完成读取	6
2	完成读取	2
新写锁线程->0	正在写入
新写锁线程->0	完成写入
新写锁线程->1	正在写入
新写锁线程->1	完成写入
新写锁线程->2	正在写入
新写锁线程->2	完成写入
结论

使用ReadWriteLock实现读写操作,一体两面,读写互斥,读读共享,但是读没有完成时候其它线程写锁无法获取

从写锁——>读锁,ReentrantReadWriteLock可以降级
《Java并发编程的艺术》中关于锁降级的说明

ReentrantReadWriteLock锁降级:将写入锁降级为读锁(类似Linux文件读写权限理解,就像写权限要高于读权限一样),锁的严苛程度变强叫做升级,反之叫做降级

特性说明
公平性选择支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平
重进入该锁支持重进人,以读写线程为例:读线程在获取了读锁之后,能够再次获取读锁。而写线程在获取了写锁之后能够再次获取写锁,同时也可以获取读锁遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁
锁降级写锁的降级,降级成为了读锁

写锁的降级,降级成为了读锁

  1. 如果同一个线程持有了写锁,在没有释放写锁的情况下,它还可以继续获得读锁。这就是写锁的降级,降级成为了读锁
  2. 规则惯例,先获取写锁,然后获取读锁,再释放写锁的次序
  3. 如果释放了写锁,那么就完全转换为读锁

why? 要有这么个特性? ——> 后面解释,设计思想见《Oracle公司ReentrantWriteReadLock源码总结》

读写锁降级演示
可以降级

锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级为读锁

如果一个线程持有了写锁,在没有释放写锁的情况下,它还可以继续获得读锁。这就是写锁的降级,降级成为了读锁

在这里插入图片描述

Java8官网说明

在这里插入图片描述

翻译:重入还允许通过获取写入锁定,然后读取锁然后释放写锁从写锁到读取锁,但是从读锁升级到写锁是不可能的

锁降级的目的:锁降级是为了让当前线程感知到数据的变化,目的是保证数据可见性

案例演示1
package com.bilibili.juc.reentrantreadwritelock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockDownGradingDemo {

    public static void main(String[] args) {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

        // 例一:正常两个A、B线程
//        new Thread(() -> {
//            readLock.lock();
//            System.out.println("---A线程读取---");
//            readLock.unlock();
//        }, "A").start();
//
//        new Thread(() -> {
//            writeLock.lock();
//            System.out.println("---B线程写入---");
//            writeLock.unlock();
//        }, "B").start();


        // 例二:only one 同一个线程
        writeLock.lock();
        System.out.println("---写入---");
        // 一些其它的业务操作...

        readLock.lock();
        System.out.println("---读取---");
        // 一些其它的业务操作...

        writeLock.unlock();
        readLock.unlock();
    }

}

输出结果:
---写入---
---读取---
结论1

同一个线程的写后立刻读是可以的,即将写入锁降级为读锁是支持的,这种就是锁降级

案例演示2
package com.bilibili.juc.reentrantreadwritelock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockDownGradingDemo2 {

    public static void main(String[] args) {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

        // 例二:only one 同一个线程
        readLock.lock();
        System.out.println("---读取---");
        // 一些其它的业务操作...

        writeLock.lock();
        System.out.println("---写入---");
        // 一些其它的业务操作...

        readLock.unlock(); // 这个位置和下面那个位置效果一样

        writeLock.unlock();
//        readLock.unlock();
    }

}


输出结果:
---读取---
// ...程序未结束
结论2

如果有线程读没有完成的时候,写线程无法获取锁,必须要等着读锁释放所锁后才有机会写,这是悲观锁的策略

不可锁升级

线程获取读锁是不能直接升级为写入锁的

在这里插入图片描述

在ReentrantReadWriteLock中,当读锁被使用时,如果有线程尝试获取写锁,该写线程会被阻塞。所以,需要释放所有读锁,才可获取写锁

在这里插入图片描述

写锁和读锁是互斥的

写锁和读锁是互斥的(这里的互斥是指线程间的互斥,当前线程可以获取到写锁又获取到读锁,但是获取到了读锁不能继续获取写锁),这是因为读写锁要保持写操作的可见性。因为,如果允许读锁在被获取的情况下对写锁的获取,那么正在运行的其他读线程无法感知到当前写线程的操作

因此,分析读写锁ReentrantReadWriteLock,会发现它有个潜在的问题:

读锁结束,写锁有望;写锁独占,读写全堵

如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,见前面案例演示1和2

即ReentrantReadWriteLock读的过程中不允许写,只有等待线程都释放了读锁,当前线程才能获取写锁也就是写入必须等待,这是一种悲观的读锁,o(T_T)o,人家还在读着那,你先别去写,省的数据乱。

后续讲解StampedLock时再详细展开

分析StampedLock(后面详细讲解,会发现它改进之处在于:

读的过程中也允许获取写锁介入(相当牛B,读和写两个操作也让你“共享”(注意引号)),这样会导致我们读的数据就可能不一致所以,需要额外的方法来判断读的过程中是否有写入,这是一种乐观的读锁,o(n_n)o哈哈~。显然乐观锁的并发效率重高,但一旦有小概率的写入导致读取的数据不一致,需要能检测出来,再读一遍就行

读写锁之读写规矩,解释为什么要锁降级?
Oracle公司ReentrantReadWriteLock源码总结

**锁降级 ** 下面的示例代码摘自ReentrantReadWriteLock源码中:

ReentrantReadWriteLock支持锁降级,遵循按照获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁,不支持锁升级

* <p><b>Sample usages</b>. Here is a code sketch showing how to perform
* lock downgrading after updating a cache (exception handling is
* particularly tricky when handling multiple locks in a non-nested
* fashion):
*
* <pre> {@code
* class CachedData {
*   Object data;
*   volatile boolean cacheValid;
*   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
*
*   void processCachedData() {
*     rwl.readLock().lock();
*     if (!cacheValid) {
*       // Must release read lock before acquiring write lock
*       rwl.readLock().unlock();
*       rwl.writeLock().lock();
*       try {
*         // Recheck state because another thread might have
*         // acquired write lock and changed state before we did.
*         if (!cacheValid) {
*           data = ...
*           cacheValid = true;
*         }
*         // Downgrade by acquiring read lock before releasing write lock
*         rwl.readLock().lock();
*       } finally {
*         rwl.writeLock().unlock(); // Unlock write, still hold read
*       }
*     }
*
*     try {
*       use(data);
*     } finally {
*       rwl.readLock().unlock();
*     }
*   }
* }}</pre>

代码解读:

  1. 代码中声明了一个volatile类型的cacheValid变量,保证其可见性
  2. 首先获取读锁,如果cache不可用,则释放读锁。获取写锁,在更改数据之前,再检查一次cacheValid的值,类似双端检锁,然后修改数据,将cachevalid置为true,然后在释放写锁前立刻抢夺获取读锁;此时,cache中数据可用,处理cache中数据,最后释放读锁。这个过程就是一个完整的锁降级的过程,目的是保证数据可见性

总结一句话,同一个线程自己持有写锁时再去拿读锁,其本质相当于重入

如果违背锁降级的步骤,如果违背锁降级的步骤, 如果违背锁降级的步骤

如果当前的线程C在修改完cache中的数据后,没有获取读锁而是直接释放了写锁,那么假设此时另一个线程D获取了写锁并修改了数据,那么C线程无法感知到数据已被修改,则数据出现错误

面试题:有没有比读写锁更快的锁?

邮戳锁StampedLock

无锁无序->加锁->读写锁->邮戳锁

是什么

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

叫做邮戳锁,也叫票据锁

stamp(戳记,long类型):代表了锁的状态。当stamp返回零时,表示线程获取锁失败,并且当释放锁或者转换锁的时候,都要传入最初获取的stamp值

它是由饥饿问题引出

锁饥饿问题

ReentrantReadWriteLock实现了读写分离,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了

假如当前1000个线程,999个读,1个写,有可能999个读线程长时间抢到锁了,那1个写线程就悲剧了

因此当前有可能会一直存在读锁,而无法获得写锁,根本没机会写,o(T_T)o

如何解决锁饥饿问题

使用”公平“策略可以一定程度上缓解这个问题

new ReentrantReadWriteLock(true);

但是使用”公平“策略是以牺牲系统吞吐量为代价的

StampedLock类的乐观读锁方式闪亮登场
为什么闪亮

ReentrantReadWriteLock

允许多个线程同时读,但是只允许一个线程写,在线程获取到写锁的时候,其他写操作和读操作都会处于阻塞状态,读锁和写锁也是互斥的,所以在读的时候是不允许写的,读写锁比传统的synchronized速度要快很多,原因就是在于ReentrantReadWriteLock支持读并发,读读可以共享

StampedLock横空出世

ReentrantReadWriteLock的读锁被占用的时候 ,其他线程尝试获取写锁的时候会被阻塞。但是,StampedLock采取乐观获取锁后,其他线程尝试获取写锁时不会被阻塞,这其实是对读锁的优化.所以,在获取乐观读锁后,还需要对结果进行校验

一句话

对于短的只读代码段,使用乐观锁模式通常可以减少争用并提高吞吐量

StampedLock的特点

  • 所有获取锁的方法,都返回一个邮戳,stamp为零表示失败,其余都表示成功
  • 所有释放锁的方法,都需要一个邮戳,这个stamp必须是和成功获取锁时得到的stamp一致
  • StampedLock是不可重入的,危险(如果一个线程已经持有了写锁,在去获取写锁的话会造成死锁)
  • StampedLock有三种访问模式:
    • Reading(读模式悲观):功能和ReentrantReadWriteLock的读锁类似
    • Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
    • Optimistic reading(乐观读模式):无锁机制,类似与数据库中的乐观锁,支持读写并发,很乐观认为读时没人修改,假如被修改在实现升级为悲观读模式
  • 乐观读模式官方讲解
    • 乐观的阅读。仅当锁定当前未处于写入模式时,方法try0ptimstiRead()才返回非零戳记。如果自获得给定标记以来未在写入模式下获取锁定,则方法validate(long)返回true。这种模式可以被认为是读锁的极弱版本,可以随时被作者破坏。对短的只读代码使用乐观模式通常可以减少争用并提高否吐量。但是,它的使用本质上是脆弱的。乐观读取部分应该只读取字段并将它们保存在局部变量中,以便以后在验证后使用。在乐观模式下读取的字段可能非常不一致,因此仅在您熟悉数据表示以检查一致性和威重复调用方法validate()例如,在首次读取对象或数组引用,然后访问其中一个字段,元素或方法时,通常需要执行此类步骤
  • 一句话:读的过程中也允许获取写锁介入

邮戳锁StampedLock代码演示

传统的读写锁模式——读的时候写锁不能获取
package com.bilibili.juc.reentrantreadwritelock;

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

public class StampedLockDemo {

    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 + 13;
        } 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 code Block, 4 second continue...");
        for (int i = 0; i < 4; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            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) {
        StampedLockDemo resource = new StampedLockDemo();
        new Thread(() -> resource.read(), "readThread").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "come in");
            resource.write();
        }, "writeThread").start();
        
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

}

输出结果:
readThread	come in readLock code Block, 4 second continue...
readThread	正在读取中
writeThread	come in
readThread	正在读取中
readThread	正在读取中
readThread	正在读取中
readThread	获得成员变量值result: 37
写线程没有修改成功,读锁时候写锁无法介入,传统的读写互斥
writeThread	写线程准备修改
writeThread	写线程结束修改
main	number:50
乐观读模式——读的过程中也允许写锁介入
package com.bilibili.juc.reentrantreadwritelock;

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

/**
 * @ClassName: StampedLockDemo
 * @Description:
 * @Author: zhangjin
 * @Date: 2024/1/31
 */
public class StampedLockDemo2 {

    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 + 13;
        } finally {
            stampedLock.unlockWrite(stamp);
        }
        System.out.println(Thread.currentThread().getName() + "\t" + "写线程结束修改");
    }

    // 乐观读,读的过程中也允许写锁介入
    public void read() {
        long stamp = stampedLock.tryOptimisticRead();

        int result = number;

        // public boolean validate(long stamp)方法官方解释
        // 如果自发出给定标记后未完全获取锁,则返回true。如果标记为零,则始终返回false。如果图章代表当前持有的锁,则始终返回true。 使用未从tryOptimisticRead()获取的值或此锁定的锁定方法调用此方法没有定义的效果或结果
        System.out.println("4秒前 stampedLock.validate方法值(true 无修改 false有修改)" + "\t" + stampedLock.validate(stamp));

        // 故意间隔4秒钟,很乐观认为读取中没有其它线程修改过number值,具体靠判断
        for (int i = 0; i < 4; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + " 正在读取...." + i + "秒后,stampedLock.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) {
        StampedLockDemo2 resource = new StampedLockDemo2();
        new Thread(resource::read, "readThread").start();

        // 暂停2秒线程,演示读过程可以写介入
        // 如果暂停>4秒线程,这样读过程没有写介入,此时输出finally value:37
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

}

输出结果:
4秒前 stampedLock.validate方法值(true 无修改 false有修改)	true
readThread	 正在读取....0秒后,stampedLock.validate方法值(true 无修改 false有修改)	true
writeThread	 come in
writeThread	写线程准备修改
writeThread	写线程结束修改
readThread	 正在读取....1秒后,stampedLock.validate方法值(true 无修改 false有修改)	false
readThread	 正在读取....2秒后,stampedLock.validate方法值(true 无修改 false有修改)	false
readThread	 正在读取....3秒后,stampedLock.validate方法值(true 无修改 false有修改)	false
有人修改----------有写操作
从乐观读升级为悲观读
重新悲观读后result:50
readThread	finally value: 50

StampedLock的缺点

  • StampedLock不支持重入,没有Reentrant开头
  • StampedLock的悲观读锁和写锁都不支持条件变量(Condition),这个也需要主要
  • 使用StampedLock一定不要调用中断操作,即不要调用interrupt()方法,会影响性能,甚至产生意外情况
  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值