Java中synchronized关键词是一个有趣的东东,它到底锁的是什么呢?
synchronized锁的到底是什么
下面将用多个实例说明synchronized锁的到底是什么。实例将演示100个线程分别累加20,如果计算正确,将输出结果2000.
我们这段代码将synchronized放在方法上,经多次运行后,偶尔也会出现小于2000的值,说明synchronized没起作用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class SyncFunc implements Runnable {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 20; i++) {
new Thread(new SyncFunc()).start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
@Override
public synchronized void run() {
for (int i = 0; i < 100; i++) {
count++;
}
}
}
输出结果:1
1970、2000、2000
我们将synchronized换了一个位置,这回锁住的是this,按理说和上面的代码没有区别,但我们这回把this对象一并输出,让我们看看执行结果。
由结果可见,synchronized锁住相同的代码,依然会出现并发问题
。但我们发现synchronized锁住的对象内存地址不同,那么synchronized锁的是对象吗?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class SyncFunc implements Runnable {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 20; i++) {
new Thread(new SyncFunc()).start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
@Override
public void run() {
System.out.println(this);
synchronized (this) {
for (int i = 0; i < 100; i++) {
count++;
}
}
}
}
部分输出结果:1
2
3
4
5
6
7synchronize.SyncFunc@52bd51dc
synchronize.SyncFunc@6b2ce86d
synchronize.SyncFunc@7f47ad69
synchronize.SyncFunc@b9098ae
synchronize.SyncFunc@719e4b38
synchronize.SyncFunc@586f4a16
1999
将上面的代码稍做修改,将Thread的target更改为同一个对象,这回运行多次也不会出现并发问题了,而且输出this的值也是同一个对象的地址。
那么,得出结论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
24public class SyncFunc implements Runnable {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
SyncFunc syncFunc = new SyncFunc();
for (int i = 0; i < 20; i++) {
new Thread(syncFunc).start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
@Override
public void run() {
System.out.println(this);
synchronized (this) {
for (int i = 0; i < 100; i++) {
count++;
}
}
}
}
部分输出结果:1
2
3
4
5
6
7synchronize.SyncFunc@3ffc8195
synchronize.SyncFunc@3ffc8195
synchronize.SyncFunc@3ffc8195
synchronize.SyncFunc@3ffc8195
synchronize.SyncFunc@3ffc8195
synchronize.SyncFunc@3ffc8195
2000
synchronized易混淆问题
我们再将上面正确的代码修改一下,将临界区域变小,因为我们只对变量count的值做修改,那么只需要锁住count变量就行了吗?
答案是否定的,因为运行结果已经说明了一切,问题就在于count++
操作。首先因为synchronized只能对对象进行锁定,所以count不能使用基本数据类型int。Integer类型的count变量,在进行自增操作时,通过拆箱-自增-装箱
几步操作过后,count变量已经不是同一个对象了
,这意味着每个线程锁住的count变量都不尽相同。
p.s.String
类型也容易出现该问题。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class SyncFunc implements Runnable {
public static Integer count = 0;
public static void main(String[] args) throws InterruptedException {
SyncFunc syncFunc = new SyncFunc();
for (int i = 0; i < 20; i++) {
new Thread(syncFunc).start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
@Override
public void run() {
synchronized (count) {
for (int i = 0; i < 100; i++) {
count++;
}
}
}
}
输出结果:1
1856
上面说过,synchronized只能对同一个对象进行加锁,那我偏偏要锁住不同的对象呢?可以直接使用synchronized锁住该对象对应的类,这样对所有线程所有对象都有效,但弊端就是临界区域太大,影响运行时间。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class SyncFunc implements Runnable {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 20; i++) {
new Thread(new SyncFunc()).start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
@Override
public void run() {
synchronized (SyncFunc.class) {
for (int i = 0; i < 100; i++) {
count++;
}
}
}
}
输出结果:1
2000
最终总结
- synchronized锁住的是不同线程对
同一个对象
的访问,而不是锁住的代码。 - synchronized锁住的对象是
Integer
、String
等类型时需要密切注意,因为他们锁住的对象地址可能发生变化。 - synchronized可以使用
synchronized(Main.class)
的形式锁住该类,对所有线程所有实例都能起作用。