JUC并发编程基础(8)--读写锁

读写锁(重点)

悲观锁和乐观锁

悲观锁不支持并发操作,效率很低。

乐观锁通过给操作资源加入版本号,多个线程可以同时操作,谁先提交,版本号更新,后面的线程不能提交。

表锁、行锁

操作资源为一张数据表时,表锁直接锁一张表,哪怕只操作一行数据。

行锁则锁其中一行,另外的行数可以被其它线程操作,但行锁可能会出现死锁情况。

读锁、写锁

读锁:共享锁,发生死锁

写锁:独占锁,发生死锁

知识补充:内存可见性问题

小例子:

/**
 * 变量的内存可见性例子
 *
 * @author star
 */
public class VolatileExample {

    /**
     * main 方法作为一个主线程
     */
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        // 开启线程
        myThread.start();

        // 主线程执行
        for (; ; ) {
            if (myThread.isFlag()) {
                System.out.println("主线程访问到 flag 变量");
            }
        }
    }

}

/**
 * 子线程类
 */
class MyThread extends Thread {

    private boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 修改变量值
        flag = true;
        System.out.println("flag = " + flag);
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

这里控制台一直都不输出主线程访问到 flag 变量这句话,是因为主线程读取到的flag值是false,但是在MyThread线程中,明明已经将flag值设置成了true,为什么读不到呢?这就涉及到Java内存模型的知识,涉及到内存可见性问题。

Java内存模型,简称JMM,是Java虚拟机所定义的,是一种并发编程的底层模型机制。

总的来说就是JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本

可能线程更改了本地内存中的副本值,但是JMM没有及时的更新到主内存 ,导致其它线程取到的仍然是之前的值,或者说线程没能将主内存中最新值同步到工作内存中,就会导致这样的可见性问题。

内存可见性是指当一个线程修改了某个变量的值,其它线程总是能知道这个变量变化。也就是说,如果线程 A 修改了共享变量 V 的值,那么线程 B 在使用 V 的值时,能立即读到 V 的最新值。

能保证内存可见性的两种操作:

  • 加锁

    加锁的原理是比如当线程获得锁之后,会清空本地内存,然后从主内存拷贝最新的共享变量,执行代码,执行完之后刷新主内存中共享变量的值,释放锁。

  • volatile关键字

    使用 volatile 修饰共享变量后,每个线程要操作变量时会从主内存中将变量拷贝到本地内存作为副本,当线程操作变量副本并写回主内存后,会通过 CPU 总线嗅探机制告知其他线程该变量副本已经失效,需要重新从主内存中读取。

    volatile 保证了不同线程对共享变量操作的可见性,也就是说一个线程修改了 volatile 修饰的变量,当修改后的变量写回主内存时,其他线程能立即看到最新值。

读写锁的用处就在于,当写线程没写完时,可能就会有线程读了,因此加上写锁,写完再释放锁,这时候就能读了,再加上读锁,读完才能继续往里面写。

这时候就有人会问了,那这个锁和volatile关键字有啥区别呢,不都是让共享变量更新的时候立马让其他线程可见么?

我的理解是,volatile只是让共享变量更新完之后能立马更新工作内存和主内存,但在这之前读线程可能会先读,所以需要锁,若没有volatile就算写完了,可能读到的也不是更新的值。

读写锁的演变:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEEBhwgM-1657634160743)(C:\Users\aMyth\AppData\Roaming\Typora\typora-user-images\image-20220706211556728.png)]

读写锁的降级

将写入锁降级为读锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p5wYmizU-1657634160745)(C:\Users\aMyth\AppData\Roaming\Typora\typora-user-images\image-20220706211835744.png)]

阻塞队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iiIEdopo-1657634160759)(C:\Users\aMyth\AppData\Roaming\Typora\typora-user-images\image-20220706214115214.png)]

线程1 往队列中放元素,线程2从中取元素

队列满了 则放的线程阻塞,空的时候取得线程阻塞

它得好处就是这个队列的线程操作都是全自动得,啥时候需要阻塞啥时候需要唤醒不用自己操心

阻塞队列分类
  • ArrayBlockingQueue

    有数组结构组成的有界阻塞队列

  • LinkedBlockingQueue

    由链表结构组成的有(大小默认值为integer.MAX_VALUE)阻塞队列

  • DelayQueue

    使用优先级队列实现的延迟无界阻塞队列

核心方法

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值