心安

细说Java关键字synchronized(一) -- 入门案例

字数统计: 1.3k阅读时长: 4 min
2019/02/25 Share

前言

本系列博文主要讲解Java高并发中常见的关键字synchronized
Java并发编程属于中高级的知识,在读本系列博文之前,希望读者具有良好的java基础以及多线程基础。
如果你连多线程都不懂,希望读者先去了解一下多线程的基本概念以及多线程的实现方式等基础内容再来阅读本博文,才能使你有所收获。
话不多说,下面我们就进入正题吧。

synchronized的作用

先看一下官方的解释,以下内容来自Oracle官方文档。

Synchronized methods enable a simple strategy for preventing thread interference and memory consistency errors:
if an object is visible to more than one thread, all reads or writes to that object’s
variables are done through synchronized methods.

以下翻译来自google翻译,外加笔者用抠脚的四级英语做了小小的修改:

同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象的所有对于变量的读写都是通过同步方法完成的。

如果上面的解释还不懂,那么简单来说就是,如果一个方法(也不一定非要是方法,其实也可以是代码块)被synchronized关键字修饰,那么意味着同一时刻最多只能有一个线程能执行这个方法,以达到安全的效果。

小小案例

下面我们写一段简单的代码来演示synchronized关键字的作用。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class SynchronizedDemo implements Runnable {

private static SynchronizedDemo s = new SynchronizedDemo();

private static int i = 0;

@Override
public void run() {
// 注意,这里数值最好写大一点,否则可能得不到预期的结果,因为现在的计算机计算速度实在是太快了
for (int j = 0; j < 1000000; j++) {
i++;
}
}

public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);

t1.start();
t2.start();

// 这两行代码的作用是为了让 t1、t2两个线程执行完毕再执行主线程,也就是等待两个线程都执行完毕再输出i的值
t1.join();
t2.join();
System.out.println(i);
}
}

一个很简单的小例子,按照正常的思维,开启了两个线程,每一个线程都会执行run()方法的循环,每个线程循环1000000次,一共就是2000000,所以i的值应该是2000000。
但是结果却并不是这样,每次执行的结果几乎都会小于2000000(有一定的几率等于2000000)。这是为什么呢?下面我们加上synchronized关键字再试一试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class SynchronizedDemo implements Runnable {

private static SynchronizedDemo s = new SynchronizedDemo();

private static int i = 0;

@Override
public void run() {
synchronized(this) {
for (int j = 0; j < 1000000; j++) {
i++;
}
}
}

public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);

t1.start();
t2.start();

// 这两行代码的作用是为了让 t1、t2两个线程执行完毕再执行主线程,也就是等待两个线程都执行完毕再输出i的值
t1.join();
t2.join();
System.out.println(i);
}
}

这里在run()方法里面加了一个synchronized修饰的同步代码块,得到的结果就始终都是2000000。这又是为什么呢?

原因分析

run()方法中,就只有一句代码i++,但是它并不是 原子性 的。
这句代码其实包含了三步操作:

  1. 读取内存中i的值
  2. 执行i + 1操作
  3. 将i的新值写入到内存

那么就有可能发送这种情况:在某一时刻,假设内存中i的值50,此时t1线程读取到了i的值,然后执行了+1操作,此时i本来应该等于51。
但是这个51还没有来得及写入到内存中,t2线程就执行到了第一步,也就是从内存中读取i的值,此时读到的仍然是50,然后t2线程也+1,i的结果还是51,然后写入到内存,此时内存中i的值为51。
可以发现,两个线程都执行了加一操作,但是实际上只加了1,这也就造成了运行的结果小于200000。

而在第二段代码示例中,加入了synchronized关键字,就像上面所说的,同一时刻最多只有一个线程能执行这段代码。
这也就意味着,当一个线程在进入同步代码块(用synchronized关键字修饰的代码块通常被称为同步代码块)的时候,另外一个线程只能在外面等待,所以不会出现上面的情况。

本篇博文就到此结束了,后续的文章将继续为大家带来更深入的讲解。感谢阅读。

本系列博文均参考慕课网“_悟空_”老师的免费课程《Java高并发之魂:synchronized深度解析》,原视频链接《Java高并发之魂:synchronized深度解析》,如有侵权行为,请联系博主,博主将会第一时间删除本系列博文!!!

示例代码Github

原文作者:XinAnzzZ

原文链接:https://www.yuhangma.com/2019/concurrent/2019-02-25-concurrent-synchronized-01/

发表日期:February 25th 2019, 12:00:00 am

更新日期:September 26th 2019, 10:46:42 am

版权声明:(转载本站文章请注明作者和出处 心 安 – XinAnzzZ ,请勿用于任何商业用途)

CATALOG
  1. 1. 前言
  2. 2. synchronized的作用
  3. 3. 小小案例
  4. 4. 原因分析