JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制

释放双眼,带上耳机,听听看~!

JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制

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类中。

我们今天介绍就先到这里,线程的概念比较多,我们要写好几篇!!!

原文地址http://www.bieryun.com/2751.html

给TA打赏
共{{data.count}}人
人已打赏
安全技术

bootstrap栅格系统自定义列

2021-12-21 16:36:11

安全技术

从零搭建自己的SpringBoot后台框架(二十三)

2022-1-12 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索