JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制
一.静态同步函数的锁是class对象
我们在上节验证了同步函数的锁是this,但是对于静态同步函数,你又知道多少呢?
我们做一个这样的小实验,我们给show方法加上static关键字去修饰
1
2
3
4
5
6
7
8
9
10
11
12 1private static synchronized void show() {
2 if (tick > 0) {
3 try {
4 Thread.sleep(10);
5 } catch (InterruptedException e) {
6 // TODO Auto-generated catch block
7 e.printStackTrace();
8 }
9 System.out.println(Thread.currentThread() + "show:" + tick--);
10 }
11 }
12
然后我们来打印一下
发现他打印出0票了,说明他还是存在this隐患,同时也说明了一点就是他使用的锁不是this,那既然不是this,那是什么呢?
-
因为静态方法中,不可以定义this,我们可以分析,静态进内存中,内存中没有本类对象,但是一定有该类的字节码文件对象类名.class,我们可以这样同步
1
2 1synchronized (MyThread.class)
2
你就会发现,线程是安全的了
静态同步的方法,使用的锁是该字节码的对象 类名.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 1package com.lgl.hellojava;
2
3//公共的 类 类名
4public class HelloJJAVA {
5
6 public static void main(String[] args) {
7
8 /**
9 * 单例设计模式
10 */
11
12 }
13}
14
15/**
16 * 饿汉式
17 *
18 * @author LGL
19 *
20 */
21class Single1 {
22 private static final Single1 s = new Single1();
23
24 private Single1() {
25
26 }
27
28 public static Single1 getInstance() {
29 return s;
30 }
31}
32
33/**
34 * 懒汉式
35 * @author LGL
36 *
37 */
38class Single {
39 private static Single s = null;
40
41 private Single() {
42
43 }
44
45 public static Single getInstance() {
46 if (s == null) {
47 s = new Single();
48 }
49 return s;
50 }
51
52}
53
我们着重点来看懒汉式,你会发现这个s是共享数据,所以我们所以延迟访问的话,一定会出现安全隐患的,但是我们使用synchronized来修饰的话,多线程启动每次都要判断有没有锁,势必会麻烦的,所以我们可以这样写
1
2
3
4
5
6
7
8
9
10
11 1public static Single getInstance() {
2 if (s == null) {
3 synchronized (Single.class) {
4 if (s == null) {
5 s = new Single();
6 }
7 }
8 }
9 return s;
10 }
11
这样其实是比较麻烦的,我们用饿汉式比较多,懒汉式作用是延时加载,多线成访问就会有安全问题
三.多线程的死锁
我们同步当中会产生一个问题,那就是死锁
- 同步中嵌套同步
是怎么个意思?我们来实现一下这段代码
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 1package com.lgl.hellojava;
2
3//公共的 类 类名
4public class HelloJJAVA {
5
6 public static void main(String[] args) {
7
8 /**
9 * 需求:简单的卖票程序,多个线程同时卖票
10 */
11 MyThread myThread = new MyThread();
12 Thread t1 = new Thread(myThread);
13 Thread t2 = new Thread(myThread);
14
15 t1.start();
16 try {
17 Thread.sleep(10);
18 } catch (InterruptedException e) {
19 // TODO Auto-generated catch block
20 e.printStackTrace();
21 }
22 myThread.flag = false;
23 t2.start();
24 }
25
26}
27
28/**
29 * 卖票程序
30 *
31 * @author LGL
32 *
33 */
34class MyThread implements Runnable {
35
36 // 票数
37 private int tick = 100;
38
39 Object j = new Object();
40
41 boolean flag = true;
42
43 @Override
44 public void run() {
45
46 if (flag) {
47 while (true) {
48 synchronized (j) {
49 show();
50 }
51 }
52 } else {
53 while (true) {
54 show();
55 }
56 }
57
58 }
59
60 private synchronized void show() {
61 synchronized (j) {
62 if (tick > 0) {
63 try {
64 Thread.sleep(10);
65 } catch (InterruptedException e) {
66 // TODO Auto-generated catch block
67 e.printStackTrace();
68 }
69 System.out.println(Thread.currentThread() + "show:" + tick--);
70 }
71 }
72 }
73}
74
这段代码里面,this锁中又object锁,object中又this锁,就会导致死锁,不信?我们运行一下
你会看到他会停止不动了,这就是死锁,而在我们开发中,我们应该尽量避免死锁的发生。
四.线程中的通讯
线程中的通讯,是比较重要的,我们看一下这张例图
存什么,取什么
线程中通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同。我们来具体看看例子
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 1package com.lgl.hellojava;
2
3//公共的 类 类名
4public class HelloJJAVA {
5
6 public static void main(String[] args) {
7 /**
8 * 线程间通讯
9 */
10 }
11
12}
13
14// 资源
15class Res {
16 String name;
17 String sex;
18}
19
20// 输入
21class Input implements Runnable {
22
23 @Override
24 public void run() {
25
26 }
27
28}
29
30// 输出
31class Output implements Runnable {
32
33 @Override
34 public void run() {
35
36 }
37
38}
39
我们定义这些个类,对吧,一个资源,两个操作,紧接着,我们应该怎么去操作他?
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 1package com.lgl.hellojava;
2
3//公共的 类 类名
4public class HelloJJAVA {
5
6 public static void main(String[] args) {
7 /**
8 * 线程间通讯
9 */
10
11 Res s = new Res();
12 Input in = new Input(s);
13 Output out = new Output(s);
14
15 Thread t1 = new Thread(in);
16 Thread t2 = new Thread(out);
17
18 t1.start();
19 t2.start();
20
21 }
22
23}
24
25// 资源
26class Res {
27 String name;
28 String sex;
29}
30
31// 输入
32class Input implements Runnable {
33
34 private Res s;
35
36 public Input(Res s) {
37 this.s = s;
38 }
39
40 @Override
41 public void run() {
42
43 int x = 0;
44
45 while (true) {
46
47 if (x == 0) {
48 s.name = "lgl";
49 s.sex = "男";
50 } else if (x == 1) {
51 s.name = "zhangsan";
52 s.sex = "女";
53 }
54 // 交替
55 x = (x + 1) % 2;
56 }
57 }
58
59}
60
61// 输出
62class Output implements Runnable {
63
64 private Res s;
65
66 public Output(Res s) {
67 this.s = s;
68 }
69
70 @Override
71 public void run() {
72
73 while (true) {
74 System.out.println(s.name + "..." + s.sex);
75 }
76 }
77
78}
79
这样去操作,你看下输出,这里出现了一个有意思的现象
你回发现他输出的竟然有女,这就是存在了安全隐患,但是也进一步的证实了,线程间的通讯
五.线程通讯带来的安全隐患
我们线程通讯,会有安全隐患,那已经怎么去解决呢?我们是不是一来就想到了同步synchronized?其实这样做没用的, 因为你传的锁是不一样的,你要想让锁唯一,就类名.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 1package com.lgl.hellojava;
2
3//公共的 类 类名
4public class HelloJJAVA {
5
6 public static void main(String[] args) {
7 /**
8 * 线程间通讯
9 */
10
11 Res s = new Res();
12 Input in = new Input(s);
13 Output out = new Output(s);
14
15 Thread t1 = new Thread(in);
16 Thread t2 = new Thread(out);
17
18 t1.start();
19 t2.start();
20
21 }
22
23}
24
25// 资源
26class Res {
27 String name;
28 String sex;
29}
30
31// 输入
32class Input implements Runnable {
33
34 private Res s;
35
36 Object o = new Object();
37
38 public Input(Res s) {
39 this.s = s;
40 }
41
42 @Override
43 public void run() {
44
45 int x = 0;
46
47 while (true) {
48 synchronized (Input.class) {
49 if (x == 0) {
50 s.name = "lgl";
51 s.sex = "男";
52 } else if (x == 1) {
53 s.name = "zhangsan";
54 s.sex = "女";
55 }
56 // 交替
57 x = (x + 1) % 2;
58 }
59 }
60 }
61
62}
63
64// 输出
65class Output implements Runnable {
66
67 private Res s;
68
69 public Output(Res s) {
70 this.s = s;
71 }
72
73 @Override
74 public void run() {
75
76 while (true) {
77 synchronized (Input.class) {
78 System.out.println(s.name + "..." + s.sex);
79 }
80 }
81 }
82
83}
84
这样,就解决了问题了
六.多线程等待唤醒机制
我们不需要多线成高速消耗CPU,而是在适当的时候唤醒他,所以我们需要定义一个布尔值
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
105
106
107
108
109
110
111 1package com.lgl.hellojava;
2
3//公共的 类 类名
4public class HelloJJAVA {
5
6 public static void main(String[] args) {
7 /**
8 * 线程间通讯
9 */
10
11 Res s = new Res();
12 Input in = new Input(s);
13 Output out = new Output(s);
14
15 Thread t1 = new Thread(in);
16 Thread t2 = new Thread(out);
17
18 t1.start();
19 t2.start();
20
21 }
22
23}
24
25// 资源
26class Res {
27 String name;
28 String sex;
29 boolean flag = false;
30}
31
32// 输入
33class Input implements Runnable {
34
35 private Res s;
36
37 Object o = new Object();
38
39 public Input(Res s) {
40 this.s = s;
41 }
42
43 @Override
44 public void run() {
45
46 int x = 0;
47
48 while (true) {
49 synchronized (Input.class) {
50
51 if (s.flag) {
52 try {
53 // 等待线程都存放在线程池
54 wait();
55 } catch (InterruptedException e) {
56 // TODO Auto-generated catch block
57 e.printStackTrace();
58 }
59 }
60
61 if (x == 0) {
62 s.name = "lgl";
63 s.sex = "男";
64 } else if (x == 1) {
65 s.name = "zhangsan";
66 s.sex = "女";
67 }
68 // 交替
69 x = (x + 1) % 2;
70 s.flag = true;
71 // 通知
72 notify();
73 }
74 }
75 }
76
77}
78
79// 输出
80class Output implements Runnable {
81
82 private Res s;
83
84 public Output(Res s) {
85 this.s = s;
86 }
87
88 @Override
89 public void run() {
90
91 while (true) {
92 synchronized (Input.class) {
93 if (!s.flag) {
94 try {
95 wait();
96 } catch (InterruptedException e) {
97 // TODO Auto-generated catch block
98 e.printStackTrace();
99 }
100 } else {
101 System.out.println(s.name + "..." + s.sex);
102 s.flag = false;
103 notify();
104 }
105
106 }
107 }
108 }
109
110}
111
都使用在同步中,因为要对待件监视器(锁)的线程操作,所以要使用在线程中,因为只有同步才具有锁
为什么这些操作线程的方法要定义在Object类中呢?因为这些方法在操作同步线程中,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程可以被同一个锁上notify,不可以对不同锁中的线程进行唤醒,也就是说,等待和唤醒必须是同一把锁!而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
我们今天介绍就先到这里,线程的概念比较多,我们要写好几篇!!!