实现线程同步

线程同步

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。
由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized 方法和 synchronized 块。

synchronized 方法

通过在方法声明中加入 synchronized关键字来声明,语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public synchronized void Test4() {
if(ticket<=0) {
flag=false;
return ;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+ticket--);
}

synchronized 方法控制对“对象的类成员变量”的访问:每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。

synchronized 块

synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。Java 为我们提供了更好的解决办法,那就是 synchronized 块。 块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。synchronized 块:通过 synchronized关键字来声明synchronized 块,语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void Test() {
synchronized(this) {
if(ticket<=0) {
flag=false;
return ;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+ticket--);
} // 同步块执行到此处
}

同步块性能分析

在使用同步块的过程中,需要程序员根据事件应用来锁定资源,同步块锁大了效率低下,同步块锁小了无法保证线程安全。

代码示例:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
public class Synchronized_Test {

public static void main(String[] args) {
// TODO Auto-generated method stub
App12306 test =new App12306();
new Thread(test,"黄牛").start();
new Thread(test,"黑牛").start();
new Thread(test,"白牛").start();
new Thread(test,"灰牛").start();
}

}
class App12306 implements Runnable{
private int ticket=100;
private boolean flag=true;
@Override
public void run() {
// TODO Auto-generated method stub
while(flag) {
Test3(); // 最优
}
}
//同步块性能分析
/**
* 这里没有考虑没有票的时候,都需要等待,浪费了时间。效率较低
*/
public void Test() {
synchronized(this) {
if(ticket<=0) {
flag=false;
return ;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+ticket--);
} // 同步块执行到此处
}
//同步块 此方法错误
/**
* 此方法没有正确地锁住资源池。
*/
public void Test2() {
if(ticket<=0) {
flag=false;
return ;
}
synchronized(this) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+ticket--);
} // 同步块执行到此处
}
//同步块 此方法最优
/**
* 此方法正确,效率高,一般被称为double checking
*/
public void Test3() {
if(ticket<=0) {
flag=false;
return ;
}
synchronized(this) {
if(ticket<=0) {
flag=false;
return ;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+ticket--);
} // 同步块执行到此处
}
//同步方法,不推荐使用!!
public synchronized void Test4() {
if(ticket<=0) {
flag=false;
return ;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+ticket--);
}
}
原创技术分享,您的支持将鼓励我继续创作
0%