欢迎访问欧博亚洲(Allbet Game)!

首页科技正文

济南日报:我去,你竟然还不会用 synchronized

admin2020-05-127

二哥,离你上一篇我去已经已往两周时间了,这个系列还不计划更新吗?着急着看呢。

以上是读者 Jason 发来的一条信息,不看不知道,一看真的是吓一跳,上次我去是 4 月 3 号更新的,离现在一个多月了,可不只是两周时间啊。可能我自己天天写,没以为时间已经已往这么久了,是时刻带来新的一篇“我去”了。

这次没有代码 review,是同事小王直接问我的,“青哥,能给我详细地说一说 synchronized 关键字怎么用吗?”他问的态度很谦逊,但我照样忍不住扬声恶骂:“我擦,小王,你丫的竟然不会用 synchronized,我当初是怎么面试你进来的!”

(我笔名是缄默王二,读者都叫二哥,但在公司不是的,同事叫我青哥,想知道我真名的,可以搜《Web全栈开发进阶之路》)

简朴地说,当两个或者两个以上的线程统一时间要修改统一个可变的共享数据时,就需要一些珍爱措施,否则,共享数据修改后的效果大概率会超出你的预期。对于初学者来说,synchronized 关键字就是最好用的一种解决方案。

01、为什么需要珍爱

可能许多初学者不明白,为什么多线程环境下,可变共享变量修改后的效果会超出预期。为了注释清晰这一点,来看一个例子。

public class SynchronizedMethod {
    private int sum;

    public int getSum() {
        return sum;
    }

    public void setSum(int sum) {
        this.sum = sum;
    }

    public void calculate() {
        setSum(getSum() + 1);
    }
}

SynchronizedMethod 是一个异常简朴的类,有一个私有的成员变量 sum,对应的 getter/setter,以及给 sum 加 1 的 calculate() 方式。

然后,我们来给 calculate() 方式写一个简朴的测试用例。

可能一些初学者还不知道怎么快速建立测试用例,我这里利市摸手地现场教学下。

第一步,把鼠标移动到类名上,会弹出一个提示框。

第二步,点击「More actions」按钮,会弹出以下提示框。

第三步,选择「Create Test」,弹出建立测试用例的对话框。

济南日报:我去,你竟然还不会用 synchronized 第1张

选择最新的 JUnit5,若是项目之前没有引入 JUnit5 依赖的话,IDEA 会提醒你,点击 Fix,IDEA 会自动帮你添加,异常智能化。在对话框中勾选要建立测试用例的方式——calculate()

点击 OK 按钮后,IDEA 会在 src 的同级目录 test 下建立一个名为 SynchronizedMethodTest 的测试类:

class SynchronizedMethodTest {
    @Test
    void calculate() {
    }
}

calculate() 方式上会有一个 @Test 的注解,示意这是一个测试方式。添加详细的代码,如下所示:

ExecutorService service = Executors.newFixedThreadPool(3);
SynchronizedMethod summation = new SynchronizedMethod();

IntStream.range(01000)
        .forEach(count -> service.submit(summation::calculate));
service.awaitTermination(1000, TimeUnit.MILLISECONDS);

assertEquals(1000, summation.getSum());

1)Executors.newFixedThreadPool() 方式可以建立一个指定巨细的线程池服务 ExecutorService。

2)通过 IntStream.range(0, 1000).forEach() 来执行 calculate() 方式 1000 次。

3)通过 assertEquals() 方式举行判断。

运行该测试用例,效果会是什么呢?

济南日报:我去,你竟然还不会用 synchronized 第2张

很不幸,失败了。预期的值为 1000,但现实的值是 976。这是由于多线程环境下,可变的共享数据没有获得珍爱。

02、synchronized 的用法

这么说吧,初学者在遇到多线程问题时,只要 synchronized 关键字使用适合,问题就能够迎刃而解。记得我刚回洛阳的时刻,面试官问我,项目中是怎么解决并发问题的呢?我就说用 synchronized 关键字,至于其他的一些锁机制,我那时刻还不知道。

嗯,面试官似乎也不知道,由于小公司嘛,并发的量级有限,性能也不用考量得太过深入(大公司的读者可以呵呵了)。接下来,就随我来,一起看看 synchronized 最常见的三种用法吧。

1)直接用在方式上,就像下面这样:

public synchronized void synchronizedCalculate() {
    setSum(getSum() + 1);
}

修改一下测试用例:

@Test
void synchronizedCalculate() throws InterruptedException {
    ExecutorService service = Executors.newFixedThreadPool(3);
    SynchronizedMethod summation = new SynchronizedMethod();

    IntStream.range(01000)
            .forEach(count -> service.submit(summation::synchronizedCalculate));
    service.awaitTermination(1000, TimeUnit.MILLISECONDS);

    assertEquals(1000, summation.getSum());
}

这时刻,再运行测试用例就通过了。由于 synchronized 关键字会对 SynchronizedMethod 工具举行加锁,统一时间内只允许一个线程对 sum 举行修改。这就似乎有一间屋子,线程进入屋子内里才可以对 sum 加 1,而 synchronized 就相当于在门上加了一个锁,一个线程进去后就锁上门,修改完 sum 后,下一个线程再进去,其他线程就在门外候着。

2)用在 static 方式上,就像下面这样:

public class SynchronizedStaticMethod {
    public static int sum;

    public synchronized static void synchronizedCalculate() {
        sum = sum + 1;
    }
}

sum 是一个静态变量,要修改静态变量的时刻,就需要把方式也酿成 static 的。

来新建一个测试用例:

class SynchronizedStaticMethodTest {
    @Test
    void synchronizedCalculate() throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(3);

        IntStream.range(01000)
                .forEach(count -> service.submit(SynchronizedStaticMethod::synchronizedCalculate));
        service.awaitTermination(1000, TimeUnit.MILLISECONDS);

        assertEquals(1000, SynchronizedStaticMethod.sum);
    }
}

静态方式上添加 synchronized 的时刻就不需要实例化工具了,直接使用类名就可以引用方式和使用变量了。测试用例也是可以通过的。

synchronized static 和 synchronized 差别的是,前者锁的是类,统一时间只能有一个线程接见这个类;后者锁的是工具,统一时间只能有一个线程接见方式。

3)用在方式块上,就像下面这样:

public void synchronisedThis() {
    synchronized (this) {
        setSum(getSum() + 1);
    }
}

这时刻,将 this 通报给了 synchronized 代码块,当在某个线程中执行这段代码块,该线程会获取 this 工具的锁,从而使得其他线程无法同时接见该代码块。若是方式是静态的,我们将通报类名取代工具引用,示例如下所示:

public static void synchronisedThis() {
    synchronized (SynchronizedStaticMethod.class) {
        sum = sum + 1;
    }
}

新建一个测试用例:

@Test
void synchronisedThis() throws InterruptedException {
    ExecutorService service = Executors.newFixedThreadPool(3);
    SynchronizedMethod summation = new SynchronizedMethod();

    IntStream.range(01000)
            .forEach(count -> service.submit(summation::synchronisedThis));
    service.awaitTermination(1000, TimeUnit.MILLISECONDS);

    assertEquals(1000, summation.getSum());
}

测试用例和 synchronized 方式的大差不差,运行后也是可以通过的。两者之间有所差别,synchronized 代码块的锁粒度要比 synchronized 方式小一些,由于 synchronized 代码块所在的方式里还可以有其他代码。

济南日报:我去,你竟然还不会用 synchronized 第3张

好了,我亲爱的读者同伙,以上就是本文的全部内容了,synchronized 的三种用法你一定掌握了吧?以为文章有点用的话,请微信搜索「缄默王二」第一时间阅读。

本文 GitHub 已经收录,有大厂面试完整考点,迎接 Star。

我是缄默王二,一枚有趣的程序员,关注即可提高学习效率。最后,请无情地址赞、珍藏、留言吧,谢谢。

,

申博Sunbet

申博Sunbet www.xzsxzxx.cn是Sunbet娱乐的官方网站,是亚洲唯一的Sunbet。公司业务主要范围:Sunbet、Sunbet、sunbet娱乐等。

转载声明:本站发布文章及版权归原作者所有,转载本站文章请注明文章来源:欧博亚洲(Allbet Game)!

本文链接:http://www.czshenhaifb.com/post/1120.html

网友评论