环境准备

首先编写一个MyPhone类,编写两个同步方法sendEmailsendEMS方法

image-20200924210837616

接着在Lock8类中的main方法中,创建两个A,B线程分别调用sendEmailsendEMS方法。由于线程创建后是有CPU调度执行的,所以A,B线程执行顺序是随机的。为了测试效果,我们让主线程睡眠200毫米,从而确保每次执行顺序都是A线程先开始调度执行。

image-20200924211303898

多次运行查看效果~,结果与预期相符。

image-20200924211333779

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
public class Lock8 {

public static void main(String[] args) throws InterruptedException {
MyPhone phone = new MyPhone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();

//保证A先启动
Thread.sleep(200);

new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}

}

class MyPhone {
public synchronized void sendEmail() throws Exception {
System.out.println("----sendEmail");

}

public synchronized void sendSMS() throws Exception {
System.out.println("----sendSMS");
}

}

1.标准访问

此时的代码和上文是一样的,一部手机,两个普通同步方法,为了测试效果,我们固定每次都是A线程先执行。

image-20200924211333779

结果是A线程执行的发送邮件先打印,后再是B线程的发送EMS打印。

解释

一个对象里面如果有多个synchronized方法,某一时刻内,只要一个线程去调用其中的一个synchronized方法,其他线程都这能等待。换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法。锁都是当前对象this,被锁定后,其他都线程都不能进入到当前对象到其他的synchronized方法。

2.邮件同步方法暂停两秒

image-20200924212339562

运行测试,两秒后先打印邮件,后在打印EMS

image-20200924211333779

解释

这里与第一种情况是一致的,同一个实例对象中,有两个普通的同步方法,某一时刻只能有一个线程去调用其中的一个同步方法,当前锁的是实例对象。所以执行步骤还是一样的。A线程先被CPU调用执行,调用sendEmail方法,被暂停2秒。当A在访问sendEmail方法期间,线程B是不能访问sendEMS方法的。所以只有等A线程执行完成后释放锁了,B线程才能访问sendEMS方法。

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
public class Lock8 {

public static void main(String[] args) throws InterruptedException {
MyPhone phone = new MyPhone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();

//保证A先启动
Thread.sleep(200);

new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}

}

class MyPhone {
public synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(2);
System.out.println("----sendEmail");

}

public synchronized void sendSMS() throws Exception {
System.out.println("----sendSMS");
}

}

3.新增一个普通方法

在资源类MyPhone中新增一个普通方法hello,线程B调用执行该方法。

image-20201012172330107

运行查看结果~

image-20200924213339826

解释

普通方法后和同步锁无关。所以当线程A调用sendEmail方法暂停两秒时,并不影响B线程调用sendEMS方法。

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
public class Lock8 {

public static void main(String[] args) throws InterruptedException {
MyPhone phone = new MyPhone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();

//保证A先启动
Thread.sleep(200);

new Thread(() -> {
try {
//phone.sendSMS();
phone.hello();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}

}

class MyPhone {
public synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(2);
System.out.println("----sendEmail");

}

public synchronized void sendSMS() throws Exception {
System.out.println("----sendSMS");
}

public void hello() throws Exception {
System.out.println("----hello");
}
}

4.两个实例对象(Phone)

线程A中是phone1调用sendEmail方法,线程B中是phone2调用sendEMS方法。

image-20200924214315379

运行查看结果~

image-20200924214344835

解释

对于普通同步方法,锁的是当前实例对象。线程A锁的是phone实例对象,线程B锁的是phone2 实例对象。所以线程A调用sendEmail方法暂停两秒时,并不影响B线程调用sendEMS方法。

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
public class Lock8 {

public static void main(String[] args) throws InterruptedException {
MyPhone phone = new MyPhone();
MyPhone phone2 = new MyPhone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();

//保证A先启动
Thread.sleep(200);

new Thread(() -> {
try {
phone2.sendSMS();
//phone.hello();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}

}

class MyPhone {
public synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(2);
System.out.println("----sendEmail");

}

public synchronized void sendSMS() throws Exception {
System.out.println("----sendSMS");
}

public static void hello() throws Exception {
System.out.println("----hello");
}
}

5.两个静态方法,同个实例对象

image-20200924214843257

运行查看结果~

image-20200924211333779

解释

对于静态同步方法,锁的是当前类的Class对象。所以A和B锁的是同一对象。B等待A完成。

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
public class Lock8 {

public static void main(String[] args) throws InterruptedException {
MyPhone phone = new MyPhone();
MyPhone phone2 = new MyPhone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();

//保证A先启动
Thread.sleep(200);

new Thread(() -> {
try {
phone.sendSMS();
//phone.hello();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}

}

class MyPhone {
public static synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(2);
System.out.println("----sendEmail");

}

public static synchronized void sendSMS() throws Exception {
System.out.println("----sendSMS");
}

public static void hello() throws Exception {
System.out.println("----hello");
}
}

6.两个静态方法,两个实例对象

image-20200924215449738

运行查看结果~

image-20200924211333779

解释

对于静态同步方法,锁的是当前类的Class对象。所以A和B锁的是同一Class对象,而不是向普通同步方法锁的各自的实例对象,因此B要等待A完成。

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
public class Lock8 {

public static void main(String[] args) throws InterruptedException {
MyPhone phone = new MyPhone();
MyPhone phone2 = new MyPhone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();

//保证A先启动
Thread.sleep(200);

new Thread(() -> {
try {
phone2.sendSMS();
//phone.hello();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}

}

class MyPhone {
public static synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(2);
System.out.println("----sendEmail");

}

public static synchronized void sendSMS() throws Exception {
System.out.println("----sendSMS");
}

public static void hello() throws Exception {
System.out.println("----hello");
}
}

7.一个静态方法,一个普通同步方法,同个实例对象

image-20200924221904037

运行查看结果~

image-20200924221935968

解释

静态同步方法锁的是Class对象,同步方法锁的是实例对象,两个锁的对象不同。所以线程A暂停,不影响线程B。

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
public class Lock8 {

public static void main(String[] args) throws InterruptedException {
MyPhone phone = new MyPhone();
MyPhone phone2 = new MyPhone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();

//保证A先启动
Thread.sleep(200);

new Thread(() -> {
try {
phone.sendSMS();
//phone.hello();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}

}

class MyPhone {
public static synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(2);
System.out.println("----sendEmail");

}

public synchronized void sendSMS() throws Exception {
System.out.println("----sendSMS");
}

public static void hello() throws Exception {
System.out.println("----hello");
}
}

8.一个静态方法,一个普通同步方法,两个实例对象

image-20200924222310807

运行查看结果~

image-20200924221935968

解释

静态同步方法锁的是Class对象,同步方法锁的是实例对象,两个锁的对象不同。线程A锁的是MyPhone.class线程B锁的是phone2实例对象,所以线程A暂停,不影响线程B。

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
public class Lock8 {

public static void main(String[] args) throws InterruptedException {
MyPhone phone = new MyPhone();
MyPhone phone2 = new MyPhone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();

//保证A先启动
Thread.sleep(200);

new Thread(() -> {
try {
phone2.sendSMS();
//phone.hello();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}

}

class MyPhone {
public static synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(2);
System.out.println("----sendEmail");

}

public synchronized void sendSMS() throws Exception {
System.out.println("----sendSMS");
}

public static void hello() throws Exception {
System.out.println("----hello");
}
}

总结

一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,

其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法

锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法

加个普通方法后发现和同步锁无关

换成两个对象后,不是同一把锁了,情况立刻变化。

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。

具体表现为以下3种形式。

对于普通同步方法,锁是当前实例对象。

对于静态同步方法,锁是当前类的Class对象。

对于同步方法块,锁是 Synchonized 括号里配置的对象

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,

可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,

所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。

所有的静态同步方法用的也是同一把锁——类对象本身,

这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。

但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,

而不管是同一个实例对象的静态同步方法之间,

还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!