JAVA中的synchronized

内置锁

每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁
内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A退出同步代码块,线程B才能获取到同一个锁,由于同一时刻只能有一个线程进入同步代码块,故同步代码块中的操作可以保证原子性

对象锁和类锁

对象锁:用于对象实例方法或一个对象实例上
类锁:用于类的静态方法或一个类的class对象上
类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁之间互不干扰,但每个类只有一个类锁
有一点必须注意的是,类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的

synchronized

synchronized可以作用于方法或代码块。线程执行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
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
class Test {
public synchronized void fun1() {
System.out.println("fun1 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun1 is working");
}
System.out.println("fun1 end");
}

public synchronized void fun2() {
System.out.println("fun2 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun2 is working");
}
System.out.println("fun2 end");
}
}

public class Main {
public static void main(String[] args) {
final Test t = new Test();
new Thread() {
@Override
public void run() {
t.fun1();
}
}.start();
new Thread() {
@Override
public void run() {
t.fun2();
}
}.start();
}
}

执行结果为fun1与fun2顺序执行:

1
2
3
4
5
6
7
8
9
10
fun1 begin
fun1 is working
fun1 is working
fun1 is working
fun1 end
fun2 begin
fun2 is working
fun2 is working
fun2 is working
fun2 end

删除两个方法的synchronized关键字后,执行结果为两个方法交替执行:

1
2
3
4
5
6
7
8
9
10
fun1 begin
fun2 begin
fun1 is working
fun2 is working
fun1 is working
fun2 is working
fun1 is working
fun1 end
fun2 is working
fun2 end

这是因为在执行synchronized修饰的方法前,线程需要先获取相应的对象锁(这里即为t的对象锁)。当第一个线程在执行t.fun1时会占有对象t的对象锁直到fun1执行结束,在此期间第二个线程由于无法获取t的对象锁,因此在t.fun1()执行完成,释放t的对象锁前无法执行被synchronized修饰的t.fun2()

易知在main函数中建立两个Test对象t1和t2时,在两个线程中分别执行t1.fun1()和t2.fun2()时,由于t1与t2为两个对象,因此两个线程中的分别执行t1.fun1()和t2.fun2()时需要获取的锁不同,因此两个函数会交替执行:

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
class Test {
public synchronized void fun1() {
System.out.println("fun1 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun1 is working");
}
System.out.println("fun1 end");
}

public synchronized void fun2() {
System.out.println("fun2 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun2 is working");
}
System.out.println("fun2 end");
}
}

public class Main {
public static void main(String[] args) {
final Test t1 = new Test();
final Test t2 = new Test();
new Thread() {
@Override
public void run() {
t1.fun1();
}
}.start();
new Thread() {
@Override
public void run() {
t2.fun2();
}
}.start();
}
}

执行结果:

1
2
3
4
5
6
7
8
9
10
fun1 begin
fun2 begin
fun1 is working
fun2 is working
fun2 is working
fun1 is working
fun1 is working
fun2 is working
fun2 end
fun1 end

但是需要注意的是,如果将Test中的fun1与fun2均改为static method,那么,这两个method就变成属于class的method了。此时再执行代码,两个线程需要的为Test.class的锁,因此会顺序执行。执行结果为:

1
2
3
4
5
6
7
8
9
10
fun1 begin
fun1 is working
fun1 is working
fun1 is working
fun1 end
fun2 begin
fun2 is working
fun2 is working
fun2 is working
fun2 end

synchronized代码块

在类Test中新建两个对象o1和o2,在fun1和fun2中分别使用synchronized(o1)和synchronized(o2)获取obj1和obj2的锁,由于两个对象的锁不同,因此使用两条线程分别调用t.fun1和t.fun2时,两个方法是交互运行的:

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
class Test {
Object obj1 = new Object();
Object obj2 = new Object();

public void fun1() {
synchronized (obj1) {
System.out.println("fun1 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun1 is working");
}
System.out.println("fun1 end");
}
}

public void fun2() {
synchronized (obj2) {
System.out.println("fun2 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun2 is working");
}
System.out.println("fun2 end");
}
}
}

public class Main {
public static void main(String[] args) {
final Test t = new Test();
new Thread() {
@Override
public void run() {
t.fun1();
}
}.start();
new Thread() {
@Override
public void run() {
t.fun2();
}
}.start();
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
fun1 begin
fun2 begin
fun2 is working
fun1 is working
fun2 is working
fun1 is working
fun1 is working
fun2 is working
fun2 end
fun1 end

删除代码中的obj1和obj2,替换为String str1 = “test”,String str2 = “test”,在fun1与fun2的synchronized中将获取锁的对象分别改为str1和str2,会发现两个方法仍然顺序执行:fun1->fun2,这是因为java中的字符串常量存于堆中,看起来str1和str2是两个不同的对象,实际上两者还是指向同一个字符串”test”的地址,因此需等待fun1执行完成后,第二个线程才能获取到字符串”test”的对象锁,进入synchronized代码段

本文首发于http://www.miaoyunze.com/,转载请注明出处