1、线程同步概述
线程之间有可能共享一些资源,比如内存、文件、数据库等。多个线程同时读写同一份共享资源时,就可能引起冲突,所以引入了线程的“同步”机制。
所谓同步,就是说线程要有先来后到,排队执行操作,而不是同时进行操作。目的就是为了防止多个线程在访问相同数据对象时,对数据造成污染和破坏。
为了实现同步,Java中提供了“锁”的机制,可以给共享资源加上一把锁,这把锁只有一把钥匙,哪个线程获取了这把钥匙,才有权利去访问该共享资源。而实现“锁”机制的关键字,就是 synchronized
2、synchronized
synchronized 的使用很简单
- 同步方法: 访问权限修饰符 synchronized 数据返回类型 方法名() { ... }
- 同步语句块:synchronized (共享对象名) { ... }
- 不能修饰构造函数、抽象方法、成员变量
下面我们来看个简单的demo:
//Data 共享数据
public class Data {
private static int count = 0;
public static int getCount() {
return count;
}
public static void add() {
count++;
}
}
//Counter 操作共享数据
public class Counter {
public synchronized void count() {
Data.add();
System.out.println("current count = " + Data.getCount());
}
}
//MyThread
public class MyThread extends Thread {
private Counter counter;
public MyThread(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for(int i = 0; i < 50; i++) {
counter.count();
}
}
}
//Test
public class Test {
public static void main(String[] args) {
Counter counter1 = new Counter();
Counter counter2 = new Counter();
Thread t1 = new MyThread(counter1);
Thread t2 = new MyThread(counter2);
t1.start();
t2.start();
}
}
//输出结果示例
...
current count = 2
current count = 2
current count = 3
current count = 4
current count = 5
current count = 6
current count = 7
...
x
1
//Data 共享数据
2
public class Data {
3
4
private static int count = 0;
5
6
public static int getCount() {
7
return count;
8
}
9
10
public static void add() {
11
count++;
12
}
13
14
}
15
16
//Counter 操作共享数据
17
public class Counter {
18
19
public synchronized void count() {
20
Data.add();
21
System.out.println("current count = " + Data.getCount());
22
}
23
24
}
25
26
//MyThread
27
public class MyThread extends Thread {
28
29
private Counter counter;
30
31
public MyThread(Counter counter) {
32
this.counter = counter;
33
}
34
35
36
public void run() {
37
for(int i = 0; i < 50; i++) {
38
counter.count();
39
}
40
}
41
42
}
43
44
45
//Test
46
public class Test {
47
public static void main(String[] args) {
48
Counter counter1 = new Counter();
49
Counter counter2 = new Counter();
50
51
Thread t1 = new MyThread(counter1);
52
Thread t2 = new MyThread(counter2);
53
t1.start();
54
t2.start();
55
}
56
}
57
58
//输出结果示例
59
...
60
current count = 2
61
current count = 2
62
current count = 3
63
current count = 4
64
current count = 5
65
current count = 6
66
current count = 7
67
...
如上例中,可以看到在 Counter 类的 count() 方法我们已经加上了 synchronized 关键字,该方法会将共享数据 Data.count 自增,然后进行打印输出。但是,我们发现输出的结果显示,count 竟然有重复,这意味着数据出现了脏读,我们在打印前,有其他线程对共享数据再次进行了修改!锁机制无效,为什么?
因为 synchronized 取得的锁是对象锁,而不是把一段代码或方法当成锁。这意味着,要实现同步,即某线程执行,其他线程等待,前提是多个线程访问的是同一个对象。而上例中,显然 counter1 和 counter2 是两个对象,我们的锁也就没有效果。
我们试着把两个线程的 Counter 类统一下,锁机制就如我们所愿了:
//Test
public class Test {
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new MyThread(counter);
Thread t2 = new MyThread(counter);
t1.start();
t2.start();
}
}
//输出结果示例
current count = 1
current count = 2
current count = 3
current count = 4
current count = 5
current count = 6
current count = 7
current count = 8
...
1
10
1
//Test
2
public class Test {
3
public static void main(String[] args) {
4
Counter counter = new Counter();
5
Thread t1 = new MyThread(counter);
6
Thread t2 = new MyThread(counter);
7
t1.start();
8
t2.start();
9
}
10
}
11
12
//输出结果示例
13
current count = 1
14
current count = 2
15
current count = 3
16
current count = 4
17
current count = 5
18
current count = 6
19
current count = 7
20
current count = 8
21
...
最后,简单总结:
- 只对改变共享资源的地方进行同步,而不是所有方法。同步块越大,多线程的效率也越低
- synchronized 关键字可以修饰方法和代码块,但是不能修饰构造函数、抽象方法、成员变量
- synchronized 关键字无法继承
- synchronized 不论在方法还是对象上,取得的锁都是对象,注意搞清楚锁定的是哪个对象