Java 并发编程梳理二:浅析 synchronized

文章目录
  1. 1. 简单介绍
  2. 2. 扩展详谈
  3. 3. 小结
  4. 4. 参考文献
  5. 5. 更多内容

Some play to play, some play to win, who are you?

开发中,遇到同步时,用的最多的就是 synchronized,以控制一段代码的并发访问。synchronized 既可以加在一段代码上,也可以加在方法上,下面进入正题,来浅析 synchronized。

简单介绍

并发编程中,多线程同时并发访问的资源即临界资源。与此同时,多个线程同时访问一个对象,操作相同的资源,分个原子操作会造成数据的不一致或不完整,故引进且采用同步机制,即确保某一时刻,一个对象只被一个线程访问。

synchronized 利用锁的机制来实现同步,锁机制特点如下:

  • 互斥 (操作的原子性):重要的特点。同一时间,只允许一个线程持有某个对象锁,来实现多线程中的协调机制。那么,同一时间只有一个线程能访问需要同步的代码块
  • 可见:锁被释放前,修改某个共享变量,之后,获得该锁的另一个线程要获得最新共享变量的值,即对另一个线程是可见的

Java 对象都有着一个实现同步的内置锁,获得该锁的唯一方法就是进入此锁保护的同步代码块或方法,调用某对象的 synchronized 方法时,即获取到该对象的同步内置锁。线程进入同步的代码块或方法时,会自动地获得这个锁;退出同步的代码块或方法时会释放这个锁。注意,同步锁也是依赖对象以存在。

举个例子,现有两个线程 T1 和 T2,都会访问对象 o 的同步锁。若某一个时刻,线程 T1 获取到 o 的同步锁,来执行一些操作,线程 T2 要获得线程 T1 持有的锁时,T2 必须等待或阻塞,直到 T1 释放这个锁!

同步方法与代码块

同步方法即 synchronized 方法如下:

1
2
3
public synchronized void sync1() {
System.out.print("synchronized method");
}

同步代码块即 synchronized 代码块如下:

1
2
3
4
5
public void sync2() {
synchronized (this) {
System.out.print("synchronized block");
}
}

this 即当前对象,可替换成替他对象。

一般来说,synchronized 方法锁的范围较大,性能较差;synchronized 代码块锁的范围较小,性能较好。

对象锁与类锁

  • 对象锁:锁在某一个实例对象上,如:

    1
    public synchronized void syncA(){}
  • 类锁:通过对象锁实现,即类的 Class 对象锁。一个类只有一个 Class 对象,故只有一个类锁,如:

    1
    public static synchronized void cSyncA(){}

扩展详谈

以下,synchronized 方法和 synchronized 代码块简单合称 synchronized 域。

synchronized 的特点如下:

  • 一个线程访问对象 o 的 synchronized 域时,其他线程对该对象 o 的该 synchronized 域其他 synchronized 域的访问将被阻塞
  • 一个线程访问对象 o 的 synchronized 域时,其他线程继续可以访问该对象 o 的非同步代码块

分别举例如下,首先看第一条

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 {

static class MyRunnable implements Runnable {
@Override
public void run() {
synchronized (this) {
try {
for (int i = 0; i < 6; i++) {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " run " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public static void main(String[] args) {
Runnable runObj = new MyRunnable();

Thread t1 = new Thread(runObj, "t1");
Thread t2 = new Thread(runObj, "t2");

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

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
t1 run 0
t1 run 1
t1 run 2
t1 run 3
t1 run 4
t1 run 5
t2 run 0
t2 run 1
t2 run 2
t2 run 3
t2 run 4
t2 run 5

synchronized 方法与此同理。

synchronized(this) 代码块在 run() 方法中执行,this 是 runObj 对象,t1 和 t2 分别基于该 runObj 对象创建线程,共享 runObj 对象的同步锁,t1 运行时,t2 会被阻塞,验证了一个线程访问对象 o 的 synchronized 域时,其他线程对该对象 o 的该synchronized 域的访问将被阻塞

还有个所谓该对象 o 其他 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class SynchronizedDemo {

private synchronized void syncMethodAA() {
try {
for (int i = 0; i < 6; i++) {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " syncMethodAA run " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private synchronized void syncMethodBB() {
try {
for (int i = 0; i < 6; i++) {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " syncMethodBB run " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();

Thread t1 = new Thread(
new Runnable() {
@Override
public void run() {
demo.syncMethodAA();
}
}, "t1");
Thread t2 = new Thread(
new Runnable() {
@Override
public void run() {
demo.syncMethodBB();
}
}, "t2");


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

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
t1 syncMethodAA run 0
t1 syncMethodAA run 1
t1 syncMethodAA run 2
t1 syncMethodAA run 3
t1 syncMethodAA run 4
t1 syncMethodAA run 5
t2 syncMethodBB run 0
t2 syncMethodBB run 1
t2 syncMethodBB run 2
t2 syncMethodBB run 3
t2 syncMethodBB run 4
t2 syncMethodBB run 5

synchronized 代码块与此同理。

新建子线程 t1 和 t2,t1 中访问该对象的一个 synchronized 方法,t2 中访问该对象的另一个 synchronized 方法,t1 运行时,t2 会被阻塞,验证了一个线程访问对象 o 的 synchronized 域时,其他线程对该对象 o 的其他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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class SynchronizedDemo {

private synchronized void syncMethodAA() {
try {
for (int i = 0; i < 6; i++) {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " syncMethodAA run " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private void methodBB() {
try {
for (int i = 0; i < 6; i++) {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " methodBB run " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();

Thread t1 = new Thread(
new Runnable() {
@Override
public void run() {
demo.syncMethodAA();
}
}, "t1");
Thread t2 = new Thread(
new Runnable() {
@Override
public void run() {
demo.methodBB();
}
}, "t2");


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

synchronized 代码块与此同理。

代码较上面略作改动。syncMethodAA() synchronized 修饰,methodBB() synchronized 修饰,新建子线程 t1 和 t2,t1 中访问该对象的一个 synchronized 方法,t2 中访问该对象的另一个 synchronized 方法,t1 运行时,t2 并没有被阻塞,是因为 t2 并没有用到同步锁。验证了一个线程访问对象 o 的 synchronized 域时,其他线程继续可以访问该对象 o 的非同步代码块

小结

综上及开发中的使用,虽然 synchronized 的使用时机很难定义,但是有一个常见情况如,程序中取出一个对象,且需要判断对象的内容值,再更新对象的内容时,大都需要 synchronized 来保护。举例,如使用 ATM 机取款,先取出用户的账户余额,扣除提款的金额,再更新一下。这种情况需要用到 synchronized 保护,想想看,以免账户有两个动作时,导致金额异常!

至此,关于 Java 并发编程梳理二完毕。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流

参考文献

1.http://www.cnblogs.com/skywang12345/p/3479202.html

2.http://langgufu.iteye.com/blog/2152608

更多内容

Java 并发编程梳理一:迈进大门

Java 并发编程梳理三:浅谈 volatile