智能社:Javascript之Vue.JS

在上一篇博客中,我“蜻蜓点水”般的介绍了下Java内存模型,在这一篇博客,我将带着大家看下Synchronized关键字的那些事,其实把Synchronized关键字放到上一篇博客中去介绍,也是符合 “Java内存模型”这个标题的,因为Synchronized关键字和Java内存模型有着密不可分的关系。但是这样,上一节的内容就太多了。同样的,这一节的内容也相当多。

好了,废话不多说,让我们开始吧,

Synchronized基本使用

首先从一个最简单的例子开始看:

public class Main {
    private int num = 0;
    private void test() {
        for (int i = 0; i < 50; i++) {
            try {
                TimeUnit.MILLISECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num++;
        }
    }
    public static void main(String[] args) {
        Main main = new Main();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                main.test();
            }).start();
        }
        try {
            TimeUnit.SECONDS.sleep(5);
            System.out.println(main.num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Main方法中开启了20个线程,每个线程执行50次的累加操作,最后打印出来的应该是50*20,也就是1000,但是每次打印出来的都不是1000,而是比1000小的数字。相信这个例子,大家早就烂熟于心了,对解决方案也是手到擒来:

public class Main {
    private int num = 0;

    private synchronized void test() {
        for (int i = 0; i < 50; i++) {
            try {
                TimeUnit.MILLISECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num++;
        }
    }

    public static void main(String[] args) {
        Main main = new Main();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                main.test();
            }).start();
        }
        try {
            TimeUnit.SECONDS.sleep(5);
            System.out.println(main.num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

只要在test方法上加一个synchronized关键字,就OK了。

Synchronized与原子性

为什么会出现这样的问题呢,可能就有一小部分人不知道其中的原因了。

这和Java的内存模型有关系:在Java的内存模型中,保证并发安全的三大特性是 原子性,可见性,有序性。导致这问题出现的原因 便是 num++ 不是原子性操作,它至少有三个操作:

  1. 读取num的值
  2. 把值写回num
  3. 执行自增操作

让我们设想有这样的一个场景:

当num=5

  1. A线程执行到num++这一步,读到了num的值为5,又把5写回了num,此时num为5(因为还没进行自增操作)。

  2. B线程也执行到了num++这一步,读到了num的值还是为5(因为A线程中的num还没有来得及进行自增操作)。

  3. A线程中的num终于进行了自增操作,num为6。

  4. B线程的num也进行了自增操作,num也为6。

可能光用文字描述,还是有点懵,所以我画了一张图来帮助大家理解:

image.png

结合文字和图片,应该就可以理解了。

可以看出来,虽然执行了两次自增操作,但是实际的效果只是自增了一次。

所以在第一段代码中,运行的结果并不是1000,而是比1000小的数字。

对于在多线程环境中,出现奇怪的结果或者情况,我们也称为“线程不安全”。

而第二段代码,就是通过Synchronized关键字,把test方法串行化执行了,也就是 A线程执行完test方法,B线程才可以执行test方法。两个线程是互斥的。这样就保证了线程的安全性,最后的结果就是1000。如果从Java内存模型的角度来说,就是保证了操作的“原子性”。

猜你喜欢

转载自blog.csdn.net/aaf122122/article/details/85230084