关于Java线程两种同步方法的理解

在阅读李兴华老师java开发实战经典一书,在java代码块级及方法级同步方法,起初讲解还是很清楚,但在讲解到模拟java多线程死锁时,使用上述代码块级同步方法让我对代码块级同步方法中同步对象的设置产生了较大的疑惑。

模拟死锁

有张三,李四两人,张三对李四说:“你给我画,我就给你书”,李四对张三说:“你给我你的书,我就给你画”。模拟代码如下:

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

/* Test.java */
public class Test {

public static void main(String[] args) {
Sisuo s1 = new Sisuo();
Sisuo s2 = new Sisuo();
s1.setFlag(true);
s2.setFlag(false);
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s2);
t1.start();
t2.start();
}
}

class Zhangsan {
public void say() {
System.out.println("你给我画,我就给你书");
}

public void get() {
System.out.println("张三得到了画");
}
}

class Lisi {
public void say() {
System.out.println("你给我书,我就给你画");
}

public void get() {
System.out.println("李四得到了书");
}
}
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
48
/* Sisuo.java */

public class Sisuo implements Runnable {

private static Zhangsan zs = new Zhangsan();
private static Lisi ls = new Lisi();
public boolean flag = false;

public void setFlag(boolean flag) {
this.flag = flag;
}

public void run() {
if (flag) {
synchronized (zs) {
System.out.println("线程" + Thread.currentThread().getName()
+ "获得了zs对象的锁");
zs.say();
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (ls) {
System.out.println("线程" + Thread.currentThread().getName()
+ "获得了ls对象的锁");
zs.get();
}
}
} else {
synchronized (ls) {
System.out.println("线程" + Thread.currentThread().getName()
+ "获得了ls对象的锁");
ls.say();
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (zs) {
System.out.println("线程" + Thread.currentThread().getName()
+ "获得了zs对象的锁");
ls.get();
}
}
}
}
}

运行结果(其中一种)

线程Thread-0获得了zs对象的锁
你给我画,我就给你书
线程Thread-1获得了ls对象的锁
你给我书,我就给你画

代码块级同步与方法级同步区别

方法级同步:

  • 实现方法:在要标志为同步的方法前加上synchronized关键字。
  • 实现原理:当调用对象的同步方法时,线程取得对象锁或监视器;如果另一个线程试图执行任何同步方 法时,他就会发现他被锁住了,进入挂起状态,直到对象监视器上的锁被释放时为止。当锁住方法的线程从方法中返回时,只有一个排队等候的线程可以访问对象。

代码块级同步:

  • 临界区:需要进行互斥的代码段,而非整个方法。
  • 实现方法:用synchronized来指定某个对象,此对象的锁被用来对花括号内的代码进行同步控制。
  • 实现原理:在进入同步代码前,必须得到object对象的锁,如果其他线程已经得到这个锁,那么就得等到锁被释放后才能进入临界区。
  • 锁的作用域:只在代码块运行的时间内。

代码运行结果分析

由于同步代码块中sleep方法的操作,使得线程t1获得zs对象的锁后睡眠,这时另一线程t2执行获得ls对象的锁然后睡眠。此后两线程无论哪个线程先醒来都需要获取另一个线程所持有的对象的锁才能继续执行,从而使得程序执行陷入死锁状态,此时Eclips控制台会有终端结束灯一直持红色状态,即程序未执行完。在对其中一同步代码块中sleep方法注释掉后,程序执行出现在未注释前从未出现的一种结果,从而进一步证明了,代码块级同步对象的设置作用。

更改后运行结果

线程Thread-0获得了zs对象的锁
你给我画,我就给你书
线程Thread-0获得了ls对象的锁
张三得到了画
线程Thread-1获得了ls对象的锁
你给我书,我就给你画
线程Thread-1获得了zs对象的锁
李四得到了书