美文网首页
谈谈并发设计中的锁和资源

谈谈并发设计中的锁和资源

作者: 写bug写bug | 来源:发表于2024-02-16 16:11 被阅读0次

在写并发程序的时候,「锁」和「资源」是两个不同的东西,如果没有弄清楚,有时会出现锁不住,或是锁错资源的情况。

以生活化的例子来说,锁和资源它们俩就像是钥匙跟抽屉的对应关系。

有时候一个钥匙可以对应多个抽屉,有时候一个钥匙只能开一个抽屉,或者是多个钥匙开很多层才可以开一个抽屉。 虽然概念上看似很简单,但在实际的架构中或是程序语法里面不一定很容易地看出来。身为工程师,当然是写Code 表达最清晰,接下来我用几段简单的 Java 代码来呈现这个概念

找找看锁在哪里?

举个例子:下面的 Java 代码定义了 TV 这个类,同时有  watchMovie 跟  playVideoGame 两个方法。并且在  watchMovie 和  playVideoGame 加上  synchronized 关键字, 目的是为了让一台 TV 不能同时执行  watchMovie 以及   playVideoGame

这段代码,你看得出来那个部分是「资源」而哪个部分表示「锁」吗?(想想钥匙和抽屉的例子,哪里是抽屉?哪里是钥匙?)

public class TV {
    public  synchronized  void  watchMovie () throws InterruptedException {
        System.out.printin("watch movie"); //资源A
        Thread.sleep(5000);
    }

    public  synchronized  void  playVideoGame () throws InterruptedException {
        System.out.println("play video game"); //资源B
        Thread.sleep(5000);
    }
}

在我用注释的地方写著资源 A 和资源 B,分别表示 watch movie 以及  play video game 这两件事。watch movie 以及  play video game 这两件事就是「资源」的部分。为了避免资源 A 和资源 B 被同时执行,我们需要一把锁,那么「锁」的位置又在哪呢?我们将上述的代码转换成下面的格式会更好理解:

public class TV {
    public void watchMovie () throws InterruptedException {
         synchronized ( this ) { //锁
            System.out.println("watch movie"); //资源A
            Thread.sleep(5000);
        }
    }

    public void  playVideoGame () throws InterruptedException {
         synchronized  ( this ) { //锁
            System.out.println("play video game"); //资源B
            Thread.sleep(5000);
        }
    }
}

我们将  synchronized 从方法上移除,改为在方法内建立一个  synchronized 的代码块,这两种写法意义上是一样的。而  synchronized 括号内的  this ,就表示进入这个区块前,要先取得的「锁」。其中,Java 的  this 是 TV 类实例化后的值,也就是说资源 A 和资源 B 被同一把锁 (就是this) 给锁住,以这种方式避免资源 A 和资源 B 被同时执行。锁的位置跟资源的位置,在上图中我用注释清楚地标示了出来。

我们还可以改写一下程序,更明显地把「锁」呈现出来,下图我用一个变量  lock 当作锁,来锁住资源 A 和资源 B:

public class TV {
    // 只有一把锁
    private final Object lock = new Object();

    public void watchMovie () throws InterruptedException {
         synchronized ( lock ) { //锁
            System.out.println("watch movie"); //资源 A
            Thread.sleep(5000);
        }
    }

    public void playVideoGame () throws InterruptedException {
         synchronized ( lock ) { //锁
            System.out.println("play video game"); //资源 B
            Thread.sleep(5000);
        }
    }
}

接下来我们来看执行的情况 (如下图),当两个执行绪 t1 和 t2 同时执行的时候,因为 new TV( ) 时在类内部只会产生一个  lock 实例,所以只有一把锁,可以避免 t1 跟 t2 两个线程同时获得锁,达成同一时间只有一个资源被获取成功的效果。

        //tv 内部只有一个 lock 实例
        final TV tv = new TV();
        Thread t1 = new Thread(() -> {
            try {
                tv.watchMovie ();//获取资源 A
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                tv.playVideoGame ();//获取资源 B
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t1.start();
        t2.start();

锁和资源误用的情况

在明白了锁跟资源之间的独立关系后,还需要仔细的考虑锁和资源的对应问题:

假设刚才的程序,我们一不小心写成 new TV( ) 两次 (如下图),产生了 tv1 和 tv2 ,这时候 tv1 内部有自己的  lock 实例,而 tv2 内部也有自己的  lock 实例,各自的锁用来锁自己的资源。两个 TV 实例可以各自执行 watch movie 以及  play video game,让 watch movie 以及  play video game 事件同时发生,但这并不一定是你想要的结果。

        final TV tv1 = new TV();
        final TV tv2 = new TV();
        Thread t1 = new Thread(() -> {
            try {
                tv1.watchMovie ();//tv1 有自己的 1ock 实例
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                tv2. playVideoGame ();//tv2 有自己的 1ock 实例
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t1.start();
        t2.start();

总结

从上面的例子我们可以很清楚地知道:

在并发的程序里面,锁是锁,资源是资源,在系统设计时需要把两者分开思考。

像我们刚才举的例子,要在程序中找出隐晦的锁与资源对应关系 (例如:synchronized 语法),如果不小心弄出了两把锁 (new TV()两次产生两个  lock ),可能会出现不希望发生的情况。

这个锁与资源的观念是具体而微的,在微观上,一小段 Java 程序是这样呈现,在宏观上更复杂的系统架构,或甚至是分布式的环境下,也可以套用同样的道理。锁会有更多不同的属性 (共享锁、排他锁等等) 也会有不同的策略(悲观锁、乐观锁等等),需要考虑锁之间兼容的问题,而对资源的操作也会更复杂,之后我会再分享更多的例子。但总体来说,概念是不变的,在并发程序中,我们应该细心地去分辨当下的锁在哪里以及资源在哪里,并且小心翼翼的 设计锁和资源的对应

相关文章

  • mysql锁

    在数据库中设计锁的目的是为了处理并发问题,在并发对资源进行访问时,数据库要合理控制对资源的访问规则。 而锁就是用来...

  • 你应该了解的MySQL锁分类

    MySQL中的锁 锁是为了解决并发环境下资源竞争的手段,其中乐观并发控制,悲观并发控制和多版本并发控制是数据库并发...

  • MySQL 中有哪些锁?

    MySQL 中有哪些锁? 数据库中锁的设计初衷处理并发问题,作为多用户共享资源,当出现并发访问的时候,数据库需要合...

  • mysql悲观锁以乐观锁

    概念 悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念。 悲观锁(Pessimist...

  • MySQL --- 锁机制

    锁是计算机协调多个进程或线程并发访问某一资源的机制。数据库锁设计的初衷是处理并发问题。作为多用户共享的资源,当出现...

  • 06全局锁和表锁

    [TOC]今天我要跟你聊聊 MySQL 的锁。数据库锁设计的初衷是处理并发问题。作为多用户共享的资源,当出现并发访...

  • MySQL的悲观锁和乐观锁

    悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念。本文将对这两种常见的锁机制在数据库...

  • 乐观锁悲观锁的实现

    悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念。本文将对这两种常见的锁机制在数据库...

  • Consul 之分布式锁

    锁的种类 在并发编程中,我们首先会学习到的知识点就是 锁 ,锁能够 简单有效 地解决并发编程中 共享资源竞争 的问...

  • MySQL 全局锁和表锁 :给表加个字段怎么有这么多阻碍?

    今天我要跟你聊聊 MySQL 的锁。数据库锁设计的初衷是处理并发问题。作为多用户共享的资源,当出现并发访问的时候,...

网友评论

      本文标题:谈谈并发设计中的锁和资源

      本文链接:https://www.haomeiwen.com/subject/fkzyadtx.html