第2章 对象及变量的并发访问
2.1 synchronized 关键字
Java中每个对象都持有一把锁,当多线程并发地访问同一对象时,可以用synchronized
关键字对这些并发线程进行同步。
如果线程A先拿到了锁,其他线程B/C/D只能傻傻地等待线程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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60class MyThreadA extends Thread {
private SycTest sycTestRef;
public MyThreadA(SycTest ref) {
this.sycTestRef = ref;
}
public void run() {
super.run();
sycTestRef.opsOnNum("set100");
}
}
class MyThreadB extends Thread {
private SycTest sycTestRef;
public MyThreadB(SycTest ref) {
this.sycTestRef = ref;
}
public void run() {
super.run();
sycTestRef.opsOnNum("set200");
}
}
public class SycTest {
private int num = 0;
public synchronized void opsOnNum(String str) {
if (str.equals("set100")) {
num = 100;
System.out.println("set 100 over!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
num = 200;
System.out.println("set 200 over!");
}
System.out.println("op is " + str + " num= " + num);
}
public static void main(String[] args) {
SycTest sycTest=new SycTest();
MyThreadA threadA=new MyThreadA(sycTest);
MyThreadB threadB=new MyThreadB(sycTest);
threadA.start();
threadB.start();
}
}
但是,当多线程并发访问不同对象时,是不需要考虑同步的。
实际上,这其实是线程安全的。例如上例子中,创建两个SynTest
对象,分别传给两个线程的构造函数,两者完全不需要考虑同步。
!!!注意!!! 如果一个类中有多个被synchronized
关键字修饰的方法(methodA()
和methidB()
),
如果此时线程A访问方法A,线程B访问方法B,并且假设线程A先得到时间片。那么线程B一定会等线程A执行完释放锁后再运行。
锁整个方法是效率不高的。
2.2 脏读 (dirty-read)
所谓脏读就是指,在修改共享变量得过程中(还没完全修改完成),此时读了共享变量的值。
脏读的解决办法就是用synchronized
去同步方法。
Here is an example: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/**
* @ClassName DirtyReadTest
* @Deacription // TODO
* @Author LiuZhian
* @Date 2019-11-01 15:08
* @Version 1.0
**/
class MyThreadC extends Thread {
DirtyReadTest dirtyReadTest;
public MyThreadC(DirtyReadTest ref) {
this.dirtyReadTest = ref;
}
public void run() {
super.run();
dirtyReadTest.changeNums(50,100);
}
}
public class DirtyReadTest {
private int num1 = 5;
private int num2 = 10;
synchronized public void changeNums(int value1,int value2) {
try {
num1 = value1;
Thread.sleep(2000);
num2=value2;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " num1 = " + num1+ ", num2 = " + num2);
}
synchronized public void printNums() {
System.out.println(Thread.currentThread().getName() + " num1 = " + num1+ ", num2 = " + num2);
}
public static void main(String[] args) {
DirtyReadTest dirtyReadTest=new DirtyReadTest();
MyThreadC threadC=new MyThreadC(dirtyReadTest);
threadC.start();
try {
Thread.sleep(500); // main线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
dirtyReadTest.printNums(); // 这里会读到脏的数据
}
}
2.3 重入锁(ReentranLock)
所谓重入锁,也就是说,当某个线程获得到了对象锁后,该线程的其他方法也可以继续操作这个变量。
Java内部有一个记录重入锁访问次数的变量,当该变量为0时,才释放锁;当该变量大于0,当前线程一直占用锁。
当线程出现异常时,该线程占用的锁将被自动释放。
2.4 Synchronized 对象监视器
- 锁的对象监视器的this
- 锁的对象监视器是任意其他对象(一般为类的实例变量和方法参数)
- 锁的对象监视器是Class类对象(即给static对象或static方法上锁)
总之,不同线程请求的是同一个锁时,他们之间只有一个进程会拿到锁,其他被阻塞;而如果不同线程请求的是不同的锁,他们之间的运行是异步的。
以下分别是对任意对象、对Class类对象上锁的情况: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
99
100
101
102
103
104package ch02;
/**
* @ClassName SynDiffObjsTest
* @Deacription // TODO
* @Author LiuZhian
* @Date 2019-11-07 18:20
* @Version 1.0
**/
public class SynDiffObjsTest {
public static void main(String[] args) {
Service sv = new Service();
ThreadA threadA = new ThreadA(sv);
ThreadB threadB = new ThreadB(sv);
ThreadC threadC = new ThreadC(sv);
threadA.setName("Thread-A");
threadB.setName("Thread-B");
threadC.setName("Thread-C");
threadC.start();
threadA.start();
threadB.start();
}
}
class Service {
private String usernameParam;
private String pwdParam;
private String anyStr = "";
public void setUsernameAndPwd(String username, String pwd) {
synchronized (anyStr) {
System.out.println(Thread.currentThread().getName() + " enters the setUsernameAndPwd block!");
usernameParam = username;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
pwdParam = pwd;
System.out.println(Thread.currentThread().getName() + " leaves the setUsernameAndPwd block!");
}
}
public synchronized void otherMethod()
{
System.out.println(Thread.currentThread().getName() + " enters the otherMethod block!");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " leaves the otherMethod block!");
}
}
class ThreadA extends Thread {
Service ref;
public ThreadA(Service ref) {
super();
this.ref = ref;
}
public void run() {
super.run();
ref.setUsernameAndPwd("a", "aaa");
}
}
class ThreadB extends Thread {
Service ref;
public ThreadB(Service ref) {
super();
this.ref = ref;
}
public void run() {
super.run();
ref.setUsernameAndPwd("b", "bbb");
}
}
class ThreadC extends Thread {
Service ref;
public ThreadC(Service ref) {
super();
this.ref = ref;
}
public void run() {
super.run();
ref.otherMethod();
}
}
输出如下:1
2
3
4
5
6Thread-C enters the otherMethod block!
Thread-A enters the setUsernameAndPwd block!
Thread-A leaves the setUsernameAndPwd block!
Thread-B enters the setUsernameAndPwd block!
Thread-C leaves the otherMethod block!
Thread-B leaves the setUsernameAndPwd block!
1 | public class StaticSynTest { |
输出如下:1
2
3
4
5
6Thread-A enters printA!
Thread-C enters printC!
Thread-B enters printB!
Thread-A leaves printA!
Thread-C leaves printC!
Thread-B leaves printB!
2.5 死锁 deadlock
一旦出现你等我,我等你
的情况,就有极大可能出现死锁。如下是一个设计的死锁程序: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
69public class DeadLockTest {
public static void main(String[] args) {
DeadThread dt=new DeadThread();
dt.setName("a");
Thread th1=new Thread(dt);
th1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
dt.setName("b");
Thread th2=new Thread(dt);
th2.start();
}
}
class DeadThread implements Runnable {
private String name;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void setName(String name) {
this.name = name;
}
public void run() {
if (this.name.equals("a"))
{
synchronized (lock1)
{
System.out.println("Name = "+this.name);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2)
{
System.out.println("代码按照 lock1 -> lock2的顺序执行完了!");
}
}
}
if (this.name.equals("b"))
{
synchronized (lock2)
{
System.out.println("Name = "+this.name);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1)
{
System.out.println("代码按照 lock2 -> lock1的顺序执行完了!");
}
}
}
}
}
这时可以用Java自带的一些命令来检测死锁是否发生: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# 1. 用jps显示所有的Java进程
C:\Users\Administrator> jps
16448 DeadLockTest
20440
10940 Jps
11100 Launcher
15228 KotlinCompileDaemon
# 2.jstack -l <tid>检测死锁
Java stack information for the threads listed above:
===================================================
"Thread-1":
at ch02.DeadThread.run(DeadLockTest.java:74)
- waiting to lock <0x00000000d5fa9ff8> (a java.lang.Object)
- locked <0x00000000d5faa008> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at ch02.DeadThread.run(DeadLockTest.java:57)
- waiting to lock <0x00000000d5faa008> (a java.lang.Object)
- locked <0x00000000d5fa9ff8> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.