Java多线程核心技术阅读笔记(第二章)

Java多线程 对象及变量的并发访问

Posted by 刘知安 on 2019-11-07
文章目录
  1. 第2章 对象及变量的并发访问
    1. 2.1 synchronized 关键字
    2. 2.2 脏读 (dirty-read)
    3. 2.3 重入锁(ReentranLock)
    4. 2.4 Synchronized 对象监视器
    5. 2.5 死锁 deadlock

第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
60
class MyThreadA extends Thread {
private SycTest sycTestRef;

public MyThreadA(SycTest ref) {
this.sycTestRef = ref;
}

@Override
public void run() {
super.run();
sycTestRef.opsOnNum("set100");

}
}

class MyThreadB extends Thread {
private SycTest sycTestRef;

public MyThreadB(SycTest ref) {
this.sycTestRef = ref;
}

@Override
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;
}

@Override
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
104
package 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;
}

@Override
public void run() {
super.run();
ref.setUsernameAndPwd("a", "aaa");
}
}


class ThreadB extends Thread {
Service ref;

public ThreadB(Service ref) {
super();
this.ref = ref;
}

@Override
public void run() {
super.run();
ref.setUsernameAndPwd("b", "bbb");
}
}

class ThreadC extends Thread {
Service ref;

public ThreadC(Service ref) {
super();
this.ref = ref;
}

@Override
public void run() {
super.run();
ref.otherMethod();
}
}

输出如下:

1
2
3
4
5
6
Thread-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
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
public class StaticSynTest {

public static void main(String[] args) {
S s= new S();
S s1=new S();
ThA threadA = new ThA();
ThB threadB = new ThB(s);
ThC threadC = new ThC(s1);
threadA.setName("Thread-A");
threadB.setName("Thread-B");
threadC.setName("Thread-C");
threadC.start();
threadA.start();
threadB.start();

}
}

class S {


synchronized public static void printA() {
System.out.println(Thread.currentThread().getName() + " enters printA!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " leaves printA!");
}

synchronized public void printB() {
System.out.println(Thread.currentThread().getName() + " enters printB!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " leaves printB!");
}

synchronized public void printC() {
System.out.println(Thread.currentThread().getName() + " enters printC!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " leaves printC!");
}
}

class ThA extends Thread {

@Override
public void run() {
super.run();
S.printA();
}
}


class ThB extends Thread {
S ref;

public ThB(S ref) {
super();
this.ref = ref;
}

@Override
public void run() {
super.run();
ref.printB();
}
}

class ThC extends Thread {
S ref;

public ThC(S ref) {
super();
this.ref = ref;
}

@Override
public void run() {
super.run();
ref.printC();
}
}

输出如下:

1
2
3
4
5
6
Thread-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
69
public 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;
}

@Override
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.