java多线程技术_Java多线程技术学习笔记(一)

目录:

一、概述 目录

首先得了解进程,打开我们电脑的windows资源管理器,可以直观看到进程的样子:

eadfd03f831d7aa1e032172f370d47c6.png

进程直观上理解就是正在进行的程序。而每个进程包含一个或者多个线程。也就是说一个进程是由若干线程组成的,在程序执行期间,真正执行的是线程,而进程只是负责给该进程中的线程分配执行路径,

所以,线程就是进程中负责程序执行的控制单元(执行路径),一个进程可以有多个执行路径,称为多线程。就像我们再使用QQ给多个好友聊天一样,每一个聊天过程都是一个线程,这些线程都属于QQ这个进程。

而开启多线程就是为了同时运行多部分代码。每一线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

二、多线程的好处与弊端

上一部分说到多线程是为了同时运行多部分代码,但是对于一个cpu而言,在每一个时刻只能执行一个线程,它会在不同线程之间快速切换,由于切换速度很快,所以感觉上去像是多个线程在"同时"执行,现在虽然出现多核技术核数是几乎不可能多过线程数的,所以仍然需要cpu不断在多个线程之间切换,以提高cpu的利用效率。 然而,但是每一个线程都需要一定的内存空间去执行,线程一多,内存空间不足,就会使得电脑显得特别卡,这就是多线程的弊端。注意到cpu在线程之间的切换是随机的。

三、JVM中的多线程解析

JVM启动时就启动了多个线程,至少有两个线程可以分析出来:一个是执行main函数的线程(也称为主线程),另一个是负责垃圾回收的线程。

在JVM垃圾回收方法是finalize方法,该方法由垃圾回收器来调用,而gc() 方法是用来运行垃圾回收器的:

c91cc4f6f28c9c1a8d2c2f5299d673ff.png

29aff3f8bc9d6643e3e76bae99d56740.png

下面展示主线程和垃圾回收线程的运行:

1 packagethread.demo;2

3 class Demo extendsObject{4 public voidfinalize(){5 System.out.println("demo ok");6 }7 }8 public classThreadDemo_1 {9

10 /**

11 *@paramargs12 */

13 public static voidmain(String[] args) {14 //TODO Auto-generated method stub

15 newDemo();16 newDemo();17 System.gc();18 newDemo();19 System.out.println("Hello World!");20 }21

22 }

运行结果:

Hello World!

demo ok

demo ok

可以发现,虽然第19行在第17行代码(垃圾回收)之前,但是第19行代码却先执行,怎么回事呢?因为垃圾回收(第17行)是由垃圾回收线程执行,而第19行代码主线程的部分,cpu从主线程开始执行,然后在主线程和垃圾回收线程之间切换,创建完两个Demo()对象之后,虽然我们调用垃圾回收器,但是垃圾回收程序还没来得及执行,cpu切换到了主线程,于是先打印出了“Hello World!”

于是虽然第19行代码,执行完了,看似整个程序都执行完了,但是JVM(Java 虚拟机)还没有结束,即虽然主线程结束了,但是JVM还要执行垃圾回收线程。

四、多线程创建方式之一:继承Thread类

首先看一看一个简单的打印程序:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagethread.demo;2 classDemo_2{3 privateString name;4 Demo_2(String name){5 this.name =name;6 }7 public voidshow(){8 for (int x = -99999999; x < 99999999; x++){};9 for (int i = 0; i < 10; i++){10 System.out.println(name + "...i" +i);11 }12 }13 }14 public classThreadDemo_2 {15

16 /**

17 *@paramargs18 */

19 public static voidmain(String[] args) {20 //TODO Auto-generated method stub

21 Demo_2 d1 = new Demo_2("旺财");22 Demo_2 d2 = new Demo_2("xiaoqiang");23 d1.show();24 d2.show();25 }26

27 }

View Code

运行结果:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

旺财...i0

旺财...i1

旺财...i2

旺财...i3

旺财...i4

旺财...i5

旺财...i6

旺财...i7

旺财...i8

旺财...i9

xiaoqiang...i0

xiaoqiang...i1

xiaoqiang...i2

xiaoqiang...i3

xiaoqiang...i4

xiaoqiang...i5

xiaoqiang...i6

xiaoqiang...i7

xiaoqiang...i8

xiaoqiang...i9

View Code

这时显示的只是主线程运行的结果,很容易理解!下面通过一种方式,让“旺财”和“xiaoqiang”的打印能够分别运行在不同的线程中,首先查看java的API文档:

ec6f7ed37a9a71af0e9695d57dda6ff1.png

翻译:线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地执行多个执行线程。

接着文档中给出了创建线程的方法:

a407d0a703506fa725fc67c81693bafb.png

总结出来就是继承Thread类然后重写Thread类中的run()方法,run方法中的代码就是线程要执行的代码,然后调用start方法开启一个线程。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagethread.demo;2 class Demo_2 extendsThread {3 privateString name;4 Demo_2(String name){5 this.name =name;6 }7 public voidrun() {8 show();9 }10 public voidshow(){11 for (int x = -99999999; x < 99999999; x++){};12 for (int i = 0; i < 10; i++){13 System.out.println(name + "...i" +i);14 }15 }16 }17 public class ThreadDemo_2 extendsThread {18

19 /**

20 *@paramargs21 */

22 public static voidmain(String[] args) {23 /*创建线程的目的是为了开启一条执行路径,去运行指定代码和24 其他代码实现同时运行,而运行的指定代码就是这个执行路径的25 任务。 JVM创建的主线程的任务都定义在主函数中。26 而自定义的线程它的任务:27 Thread类用于描述线程,线程需要任务,所以Thread类也有对28 任务的描述,这个任务就通过Thread类中的run方法来体现的。29 也就是说,run方法就是封装自定义线程任务的函数。30 run方法中定义的就是线程要运行的任务代码!!!31

32 开启线程是为了运行指定代码,所以只有继承Thread类,并重写run方法。33 并将运行代码定义在run方法中即可34 */

35 Demo_2 d1 = new Demo_2("旺财");36 Demo_2 d2 = new Demo_2("xiaoqiang");37 d1.start();38 d2.start();39 }40

41 }

View Code

运行结果:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

xiaoqiang...i0

旺财...i0

xiaoqiang...i1

旺财...i1

旺财...i2

旺财...i3

xiaoqiang...i2

xiaoqiang...i3

xiaoqiang...i4

xiaoqiang...i5

xiaoqiang...i6

xiaoqiang...i7

旺财...i4

xiaoqiang...i8

xiaoqiang...i9

旺财...i5

旺财...i6

旺财...i7

旺财...i8

旺财...i9

View Code

可以看出,运行结果正如上面分析的一样,cpu在多个线程之间随机切换,于是打印出的结果与上面只有主线程时结果差别很大。

下面我们让主线程参与进来,即同时看看线程d1,d2和主线程的运行结果:

1 packagethread.demo;2 class Demo_2 extendsThread {3 privateString name;4 Demo_2(String name){5 this.name =name;6 }7 public voidrun() {8 show();9 }10 public voidshow(){11 for (int x = -99999999; x < 99999999; x++){};12 for (int i = 0; i < 10; i++){13 System.out.println(name + "...i" +i);14 }15 }16 }17 public class ThreadDemo_2 extendsThread {18

19 /**

20 *@paramargs21 */

22 public static voidmain(String[] args) {23 /*创建线程的目的是为了开启一条执行路径,去运行指定代码和24 其他代码实现同时运行,而运行的指定代码就是这个执行路径的25 任务。 JVM创建的主线程的任务都定义在主函数中。26 而自定义的线程它的任务:27 Thread类用于描述线程,线程需要任务,所以Thread类也有对28 任务的描述,这个任务就通过Thread类中的run方法来体现的。29 也就是说,run方法就是封装自定义线程任务的函数。30 run方法中定义的就是线程要运行的任务代码!!!31

32 开启线程是为了运行指定代码,所以只有继承Thread类,并重写run方法。33 并将运行代码定义在run方法中即可34 */

35 Demo_2 d1 = new Demo_2("旺财");36 Demo_2 d2 = new Demo_2("xiaoqiang");37 d1.start();38 d2.start();39 System.out.println("over");40 }41

42 }

运行结果:

over

xiaoqiang...i0

旺财...i0

xiaoqiang...i1

旺财...i1

xiaoqiang...i2

旺财...i2

xiaoqiang...i3

xiaoqiang...i4

旺财...i3

xiaoqiang...i5

旺财...i4

xiaoqiang...i6

旺财...i5

xiaoqiang...i7

旺财...i6

xiaoqiang...i8

旺财...i7

xiaoqiang...i9

旺财...i8

旺财...i9

多次执行,会发现显示结果一直变化,这就是多个线程随机占用cpu的结果。 当然,如果想看到到底是哪个线程正在执行,可以调用Thread中的currentThread().getName()方法,其中currentThread()是用来返回当前的线程对象,然后用线程对象继续调用getName()就是返回当前线程的名字。程序如下:

1 packagethread.demo;2 class Demo_2 extendsThread {3 privateString name;4 Demo_2(String name){5 this.name =name;6 }7 public voidrun() {8 show();9 }10 public voidshow(){11 for (int x = -99999999; x < 99999999; x++){};12 for (int i = 0; i < 10; i++){13 System.out.println(name + "...i" + "...name: " +getName());14 }15 }16 }17 public class ThreadDemo_2 extendsThread {18

19 /**

20 *@paramargs21 */

22 public static voidmain(String[] args) {23 /*创建线程的目的是为了开启一条执行路径,去运行指定代码和24 其他代码实现同时运行,而运行的指定代码就是这个执行路径的25 任务。 JVM创建的主线程的任务都定义在主函数中。26 而自定义的线程它的任务:27 Thread类用于描述线程,线程需要任务,所以Thread类也有对28 任务的描述,这个任务就通过Thread类中的run方法来体现的。29 也就是说,run方法就是封装自定义线程任务的函数。30 run方法中定义的就是线程要运行的任务代码!!!31

32 开启线程是为了运行指定代码,所以只有继承Thread类,并重写run方法。33 并将运行代码定义在run方法中即可34 */

35 Demo_2 d1 = new Demo_2("旺财");36 Demo_2 d2 = new Demo_2("xiaoqiang");37 d1.start();38 d2.start();39 System.out.println("over" + "..." +Thread.currentThread().getName());40 }41

42 }

运行结果:

over...main

旺财...i...name: Thread-0

旺财...i...name: Thread-0

旺财...i...name: Thread-0

旺财...i...name: Thread-0

旺财...i...name: Thread-0

旺财...i...name: Thread-0

xiaoqiang...i...name: Thread-1

xiaoqiang...i...name: Thread-1

xiaoqiang...i...name: Thread-1

xiaoqiang...i...name: Thread-1

xiaoqiang...i...name: Thread-1

xiaoqiang...i...name: Thread-1

xiaoqiang...i...name: Thread-1

xiaoqiang...i...name: Thread-1

xiaoqiang...i...name: Thread-1

xiaoqiang...i...name: Thread-1

旺财...i...name: Thread-0

旺财...i...name: Thread-0

旺财...i...name: Thread-0

旺财...i...name: Thread-0

注意主线程的名字是固定的就是main,然后自定义的线程从0开始编号。

五、线程的状态

93ecd48441690cb07ec7075df5b42290.png

六、多线程创建的方式之二:实现Runnable接口

首先来读一下API文档关于Runnable的描述:

1e7a99bb32155d5b336ef0d90b5baad7.png

划红线部分:Runnable接口由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run的无参数方法。于是采用实现接口的方式来实现多线程:

定义Runnable接口

覆盖Runnable接口中的run方法,将线程任务代码封装到run方法中。

通过Thread类创建对线,并将Runnable接口中的子类对象作为参数传递给Thread类的构造函数。因为线程任务都封装在Runnable接口子类对象的run方法中,所以在创建时必须要明确要运行的任务,如果不传递这个对象,Thread类会运行它自己的run方法,而不是我们定义在接口中的方法。

调用线程的start方法开启线程。

代码如下:

1 packagethread.demo;2 //通过实现接口的方式实现多线程创建

3 class Demo_3 implementsRunnable {4 public voidrun() {5 show();6 }7 public voidshow(){8 for (int x = -99999999; x < 99999999; x++){};9 for (int i = 0; i < 10; i++){10 System.out.println(Thread.currentThread().getName());11 }12 }13 }14

15 public class ThreadDemo_3 extendsThread {16

17 /**

18 *@paramargs19 */

20 public static voidmain(String[] args) {21 //创建一个Runnable接口的子类对象

22 Demo_3 d = newDemo_3();23 //将上述Runnable接口的子类对象传入Thread构造函数,24 //创建线程

25 Thread t1 = newThread(d);26 Thread t2 = newThread(d);27 t1.start();28 t2.start();29 }30

31 }

运行结果:

Thread-1

Thread-0

Thread-1

Thread-0

Thread-1

Thread-0

Thread-0

Thread-1

Thread-0

Thread-1

Thread-0

Thread-1

Thread-0

Thread-1

Thread-0

Thread-1

Thread-1

Thread-1

Thread-0

Thread-0

同样可以看见两个线程在随机切换执行。

细节:通过阅读API文档,可以发现,Thread类里面定义了自己的run方法,而Runnable也有run方法,而Thread的构造方法包含着下面两种(当然不止两种):

b282dbf391fdb21c9f4aeda6d1c61f8c.png

第一种构造方法不包含任何参数,那么在使用Thread创建的线程对象在运行时,就调用Thread类自己的run方法,如果传入一个Runnable子类对象,那么在使用Thread类创建对象时,运行的任务就是Runnable接口中定义run方法。原理用代码简单解释如下:

1 packagethread.demo;2

3 classThread {4 privateRunnable r;5 Thread() {}6 Thread(Runnable) {7 this.r =r;8 }9

10 public voidrun() {11 r.run();12 }13

14 public voidstart() {15 run();16 }17 }18

19 public class SubThread extendsThread {20

21 public voidrun() {22 System.out.println("dsa");23 }24

25 }

在Thread内部有一个私有的Runable子类对象,可以看出,当我们把Runable子类 r 对象传递给Thread类构造函数的时候,启动start()就会调用run(),而run()接着调用 r 的run方法;

但是当我们直接通过上面介绍的方式一,即直接继承Thread类创建线程的时候,如19-23行所示,我们需要覆盖Thread类中的run方法,那么SubThread类的对象就在通过start方法启动线程的时候调用的run方法时就会调用我们在Thread子类中自己定义的run方法(21-22行)!

七、使用方式二创建多线程的好处

创建线程的两种方式:

方式一:继承Thread

方式二:实现Runnable接口

好处:

1)将线程的任务从线程的子类中分离出来,进行了单独封装,按照面向对象的思想将任务封装成对象。

2)避免了java单继承的局限性

所以,创建线程第二种方式较为常用!!!

八、多线程示例

下面实现四个售票员(四个线程)一起卖100张票的示例。

1 packagethread.demo;2 //买票:四个售票员一起卖100张票

3 class Ticket implementsRunnable {4 private int num = 100;5 public voidrun() {6 while(true) {7 if (num > 0) {8 System.out.println(Thread.currentThread().getName() + "...sale..." + num--);9 }10 }11 }12 }13

14 public classTicketDemo {15

16

17 public static voidmain(String[] args) {18

19 Ticket t = newTicket();20 /*

21 Ticket t1 = new Ticket();22 Ticket t2 = new Ticket();23 Ticket t3 = new Ticket();24 Ticket t4 = new Ticket();25 */

26 Thread seller1 = newThread(t);27 Thread seller2 = newThread(t);28 Thread seller3 = newThread(t);29 Thread seller4 = newThread(t);30

31 seller1.start();32 seller2.start();33 seller3.start();34 seller4.start();35 }36

37 }

运行结果:

Thread-0...sale...100

Thread-3...sale...97

Thread-2...sale...98

Thread-1...sale...99

Thread-2...sale...94

Thread-3...sale...95

Thread-0...sale...96

Thread-3...sale...91

Thread-2...sale...92

Thread-2...sale...88

Thread-2...sale...87

Thread-2...sale...86

Thread-1...sale...93

Thread-1...sale...84

Thread-1...sale...83

Thread-2...sale...85

Thread-3...sale...89

Thread-0...sale...90

Thread-3...sale...80

Thread-2...sale...81

Thread-1...sale...82

Thread-1...sale...76

Thread-1...sale...75

Thread-2...sale...77

Thread-3...sale...78

Thread-0...sale...79

Thread-3...sale...72

Thread-2...sale...73

Thread-1...sale...74

Thread-2...sale...69

Thread-3...sale...70

Thread-0...sale...71

Thread-3...sale...66

Thread-2...sale...67

Thread-1...sale...68

Thread-2...sale...63

Thread-2...sale...61

Thread-3...sale...64

Thread-0...sale...65

Thread-3...sale...59

Thread-2...sale...60

Thread-1...sale...62

Thread-2...sale...56

Thread-3...sale...57

Thread-0...sale...58

Thread-3...sale...53

Thread-2...sale...54

Thread-1...sale...55

Thread-1...sale...49

Thread-2...sale...50

Thread-3...sale...51

Thread-0...sale...52

Thread-0...sale...45

Thread-3...sale...46

Thread-2...sale...47

Thread-1...sale...48

Thread-2...sale...42

Thread-3...sale...43

Thread-0...sale...44

Thread-3...sale...39

Thread-2...sale...40

Thread-1...sale...41

Thread-2...sale...36

Thread-3...sale...37

Thread-0...sale...38

Thread-3...sale...33

Thread-2...sale...34

Thread-1...sale...35

Thread-2...sale...30

Thread-3...sale...31

Thread-0...sale...32

Thread-3...sale...27

Thread-2...sale...28

Thread-1...sale...29

Thread-2...sale...24

Thread-3...sale...25

Thread-0...sale...26

Thread-3...sale...21

Thread-2...sale...22

Thread-1...sale...23

Thread-2...sale...18

Thread-3...sale...19

Thread-0...sale...20

Thread-3...sale...15

Thread-2...sale...16

Thread-1...sale...17

Thread-2...sale...12

Thread-3...sale...13

Thread-0...sale...14

Thread-3...sale...9

Thread-2...sale...10

Thread-1...sale...11

Thread-2...sale...6

Thread-3...sale...7

Thread-0...sale...8

Thread-3...sale...3

Thread-2...sale...4

Thread-1...sale...5

Thread-3...sale...1

Thread-0...sale...2

看到四个线程一起把100张票卖完了.

九、线程安全问题现象

分析上面的示例,由于线程之间随机切换执行,假如售票员0(线程0),卖到了1号票,此时 num = 1 ;

if (num > 0) {

System.out.println(Thread.currentThread().getName()+ "...sale..." + num--);

}

经过判断,满足if 条件,进入后面的代码块,但是存在一种情况就是,线程0还没有来得及执行打印语句,就切换到了线程1,此时线程0处于等待状态(等待继续执行...), 此时num仍然为1, 然后线程1判断

满足条件,顺利执行完,之后num--, 于是num = 0; 恰好随机切换到线程0, 然后线程0执行打印语句(Thread-0...sale...0),就是说售票员0 把0号票卖出去了,显然不可以!这种情况就导致了线程不安全!

为了使得程序出现我们分析的这种不安全的情况,需要在示例代码在第7行之后稍微停顿一下,然后执行后面的打印语句,如果不停顿,现在cpu的计算速度很快,判断 if (num > 0)为真之后往往很快就会执行到打印语句,上面分析的情况很难观测到,于是为了说明问题,做如下添加:

1 packagethread.demo;2 //买票:四个售票员一起卖100张票

3 class Ticket implementsRunnable {4 private int num = 100;5 public voidrun() {6 while(true) {7 if (num > 0) {8 //让线程sleep一会,好让打印语句还没来得及执行,其他线程9 //就切换进来,这样方便我们观测线程安全隐患

10 try{11 Thread.sleep(20);12 } catch(InterruptedException e) {13 //TODO Auto-generated catch block

14 e.printStackTrace();15 }16

17 System.out.println(Thread.currentThread().getName() + "...sale..." + num--);18 }19 }20 }21 }22

23 public classTicketDemo {24

25

26 public static voidmain(String[] args) {27

28 Ticket t = newTicket();29 /*

30 Ticket t1 = new Ticket();31 Ticket t2 = new Ticket();32 Ticket t3 = new Ticket();33 Ticket t4 = new Ticket();34 */

35 Thread seller1 = newThread(t);36 Thread seller2 = newThread(t);37 Thread seller3 = newThread(t);38 Thread seller4 = newThread(t);39

40 seller1.start();41 seller2.start();42 seller3.start();43 seller4.start();44 }45

46 }

运行结果:

Thread-3...sale...100

Thread-2...sale...98

Thread-1...sale...100

Thread-0...sale...99

Thread-0...sale...95

Thread-3...sale...97

Thread-2...sale...97

Thread-1...sale...96

Thread-2...sale...94

Thread-3...sale...94

Thread-1...sale...94

Thread-0...sale...94

Thread-0...sale...93

Thread-3...sale...91

Thread-1...sale...92

Thread-2...sale...93

Thread-3...sale...88

Thread-0...sale...89

Thread-2...sale...89

Thread-1...sale...90

Thread-0...sale...87

Thread-2...sale...84

Thread-3...sale...85

Thread-1...sale...86

Thread-2...sale...83

Thread-3...sale...82

Thread-1...sale...82

Thread-0...sale...83

Thread-0...sale...81

Thread-3...sale...80

Thread-1...sale...80

Thread-2...sale...81

Thread-3...sale...78

Thread-0...sale...79

Thread-1...sale...79

Thread-2...sale...77

Thread-2...sale...76

Thread-1...sale...75

Thread-3...sale...75

Thread-0...sale...76

Thread-2...sale...74

Thread-1...sale...73

Thread-3...sale...73

Thread-0...sale...74

Thread-0...sale...72

Thread-1...sale...70

Thread-3...sale...71

Thread-2...sale...69

Thread-0...sale...68

Thread-2...sale...65

Thread-1...sale...67

Thread-3...sale...66

Thread-1...sale...64

Thread-2...sale...61

Thread-0...sale...62

Thread-3...sale...63

Thread-2...sale...59

Thread-1...sale...57

Thread-3...sale...60

Thread-0...sale...58

Thread-2...sale...56

Thread-1...sale...55

Thread-3...sale...54

Thread-0...sale...53

Thread-1...sale...52

Thread-2...sale...51

Thread-0...sale...50

Thread-3...sale...49

Thread-2...sale...48

Thread-1...sale...47

Thread-3...sale...46

Thread-0...sale...45

Thread-1...sale...44

Thread-3...sale...42

Thread-0...sale...43

Thread-2...sale...41

Thread-1...sale...39

Thread-0...sale...38

Thread-3...sale...40

Thread-2...sale...37

Thread-2...sale...36

Thread-0...sale...34

Thread-3...sale...35

Thread-1...sale...33

Thread-1...sale...32

Thread-2...sale...31

Thread-3...sale...30

Thread-0...sale...29

Thread-0...sale...28

Thread-3...sale...27

Thread-1...sale...26

Thread-2...sale...25

Thread-1...sale...24

Thread-2...sale...23

Thread-0...sale...22

Thread-3...sale...21

Thread-3...sale...20

Thread-1...sale...18

Thread-2...sale...19

Thread-0...sale...20

Thread-2...sale...17

Thread-3...sale...15

Thread-0...sale...16

Thread-1...sale...14

Thread-3...sale...13

Thread-0...sale...12

Thread-2...sale...11

Thread-1...sale...10

Thread-0...sale...9

Thread-3...sale...8

Thread-1...sale...7

Thread-2...sale...6

Thread-3...sale...5

Thread-0...sale...4

Thread-2...sale...3

Thread-1...sale...2

Thread-3...sale...1

Thread-0...sale...0

Thread-2...sale...-1

Thread-1...sale...-2

可以看到售票员竟然卖出了0,-1,-2号票!!这就是线程安全问题的简单演示,每次运行出现的结果不一样,也有可能恰好正常,没有出现安全问题,以为线程切换的不确定性,但是这种问题一旦出现,通常很致命,所以必须注意!

十、线程安全问题产生的原因

由上面可以知道,线程安全问题必须要解决,但是解决问题关键是分析原因!根据上面示例总结出以下原因:

多个线程操作共享的数据

操作共享数据的线程代码有多条

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

十一、同步代码块

既然知道产生线程安全问题的原因,就开始着手解决。

解决思路:

将多条操作操作共享数据的代码封装起来, 当有线程在执行这些代码的时候,其他线程是不可以参与运算。

必须要当前线程把这些代码都执行完毕以后,其他线程才可以参与运算.

Java中,用同步代码块就可以解决这个问题。同步代码块的格式:

synchronized(对象){

需要被同步的代码;

}

其中的“对象”可以看做一个标记,可以使用任何类型的对象,包括自定义的对象。当然,为了方便,使用Object类的对象即可。

1 packagethread.demo;2 //买票:四个售票员一起卖100张票

3 class Ticket implementsRunnable {4 private int num = 100;5 Object obj = newObject();6 public voidrun() {7 while(true) {8 synchronized(obj) {9 if (num > 0) {10 //让线程sleep一会,好让打印语句还没来得及执行后面的打印语句,其他线程11 //就切换进来,这样方便我们观测线程安全隐患

12 try{13 Thread.sleep(20);14 } catch(InterruptedException e) {15 //TODO Auto-generated catch block

16 e.printStackTrace();17 }18

19 System.out.println(Thread.currentThread().getName() + "...sale..." + num--);20 }21 }22 }23 }24 }25

26 public classTicketDemo {27

28

29 public static voidmain(String[] args) {30

31 Ticket t = newTicket();32 /*

33 Ticket t1 = new Ticket();34 Ticket t2 = new Ticket();35 Ticket t3 = new Ticket();36 Ticket t4 = new Ticket();37 */

38 Thread seller1 = newThread(t);39 Thread seller2 = newThread(t);40 Thread seller3 = newThread(t);41 Thread seller4 = newThread(t);42

43 seller1.start();44 seller2.start();45 seller3.start();46 seller4.start();47 }48

49 }

运行结果:

Thread-0...sale...100

Thread-0...sale...99

Thread-0...sale...98

Thread-0...sale...97

Thread-0...sale...96

Thread-0...sale...95

Thread-0...sale...94

Thread-0...sale...93

Thread-0...sale...92

Thread-0...sale...91

Thread-0...sale...90

Thread-0...sale...89

Thread-0...sale...88

Thread-2...sale...87

Thread-2...sale...86

Thread-2...sale...85

Thread-3...sale...84

Thread-3...sale...83

Thread-3...sale...82

Thread-3...sale...81

Thread-1...sale...80

Thread-1...sale...79

Thread-1...sale...78

Thread-1...sale...77

Thread-1...sale...76

Thread-1...sale...75

Thread-3...sale...74

Thread-2...sale...73

Thread-2...sale...72

Thread-2...sale...71

Thread-2...sale...70

Thread-2...sale...69

Thread-2...sale...68

Thread-0...sale...67

Thread-0...sale...66

Thread-0...sale...65

Thread-0...sale...64

Thread-0...sale...63

Thread-0...sale...62

Thread-2...sale...61

Thread-2...sale...60

Thread-3...sale...59

Thread-3...sale...58

Thread-3...sale...57

Thread-3...sale...56

Thread-3...sale...55

Thread-3...sale...54

Thread-3...sale...53

Thread-3...sale...52

Thread-3...sale...51

Thread-3...sale...50

Thread-3...sale...49

Thread-3...sale...48

Thread-3...sale...47

Thread-3...sale...46

Thread-3...sale...45

Thread-3...sale...44

Thread-3...sale...43

Thread-3...sale...42

Thread-3...sale...41

Thread-1...sale...40

Thread-1...sale...39

Thread-1...sale...38

Thread-1...sale...37

Thread-1...sale...36

Thread-1...sale...35

Thread-1...sale...34

Thread-1...sale...33

Thread-1...sale...32

Thread-1...sale...31

Thread-1...sale...30

Thread-1...sale...29

Thread-1...sale...28

Thread-1...sale...27

Thread-1...sale...26

Thread-1...sale...25

Thread-3...sale...24

Thread-2...sale...23

Thread-2...sale...22

Thread-2...sale...21

Thread-0...sale...20

Thread-0...sale...19

Thread-0...sale...18

Thread-0...sale...17

Thread-2...sale...16

Thread-2...sale...15

Thread-2...sale...14

Thread-2...sale...13

Thread-3...sale...12

Thread-3...sale...11

Thread-3...sale...10

Thread-3...sale...9

Thread-3...sale...8

Thread-3...sale...7

Thread-1...sale...6

Thread-1...sale...5

Thread-1...sale...4

Thread-1...sale...3

Thread-3...sale...2

Thread-3...sale...1

可以看出,问题得到了很好地解决!

十二、同步的好处与弊端

首先讨论下,同步到底是如何实现的。

假如0线程执行到run方法的同步代码块,那么 0 线程就持有了 obj, 即obj 被加载到了0线程里面,当其他线程过来时,它们也需要加载obj才能进入同步代码块,但是在线程没有执行完同步代码块之前,obj一直被0线程占有,所以其他线程无法进入同步代码块,知道0线程执行完同步代码块释放 obj,其他线程才有机会加载obj, 然后进入同步代码块。所以obj就像一把锁一样,里面的不出来,外面的就进不去。所以常常称为同步锁!谁拿到了锁,谁就进得去,出来之后释放锁,以便其他线程有机会使用。

同步的好处:解决了线程安全问题。

同步的弊端:相对降低了效率,因为同步外的线程都会去判断同步锁。

十三、同步的前提

同步中,必须有多个线程并使用同一个锁,因为一个线程没必要同步,数据全被这一个线程使用,就不存在所谓的线程安全问题。而如果不使用同一个锁,即使一个线程在执行加了锁的代码块,其他线程同样可以通过拿到其他锁进来参与运算。

十四、同步函数

一个稍微简单点的多线程程序示例:

1 packagethread.demo;2 /*

3 需求:两个储户,每个都到银行存钱,每次存100,共存3次4 */

5 classBank {6 private intsum;7 public void add(intnum) {8 sum +=num;9 System.out.println("sum = " +sum);10 }11 }12

13 class Customer implementsRunnable {14 Bank bank = newBank();15 public voidrun() {16 for (int x = 0; x < 3; x++) {17 bank.add(100);18 }19 }20 }21

22 public classBankDemo {23

24 /**

25 *@paramargs26 */

27 public static voidmain(String[] args) {28 //TODO Auto-generated method stub

29 Customer customer = newCustomer();30 Thread t1 = newThread(customer);31 Thread t2 = newThread(customer);32

33 t1.start();34 t2.start();35

36 }37

38 }

运行结果:

sum = 100

sum = 200

sum = 300

sum = 500

sum = 400

sum = 600

当然,由于线程的随机切换,显示结果有点乱,最终两人共向银行存入了600.

分析一下这个程序:run方法里面的代码调用了add方法,sum 是两个线程的共享数据,对于共享数据的操作不止一条:

sum +=num;

System.out.println("sum = " + sum);

假如第一个用户(线程0)存入100,执行第一条语句,sum 就变为100. 正常接下来就应该输出 sum = 100, 但是这时存在一种情况:线程0 还没来得及输出,第二个用户(线程1)存入100,于是sum = 100 + 100 = 200, 线程1 然后正常输出 sum = 200,刚输出完成,线程0又切入了,接着执行打印语句,本来他存入的只是100,但是由于sum = 200, 线程0 也打印出sum = 200, 对应这实际情况就是,用户1存入了100,系统却显示存入了200,显然存在着线程安全问题。同样,为了展示这个安全问题,在上面两条语句之间假如一定的停顿:

sum +=num;try{

Thread.sleep(20);

}catch(InterruptedException e) {//TODO Auto-generated catch block

e.printStackTrace();

}

System.out.println("sum = " + sum);

运行结果:

sum = 200

sum = 200

sum = 400

sum = 400

sum = 600

sum = 600

可见,200显示了两次,肯定是有一个用户存入100时,却打印出了200. 显然不合理!

首先想到的就是同步:

1 classBank {2 private intsum;3 Object obj = newObject();4 public void add(intnum) {5 synchronized(obj) {6 sum +=num;7 try{8 Thread.sleep(20);9 } catch(InterruptedException e) {10 //TODO Auto-generated catch block

11 e.printStackTrace();12 }13 System.out.println("sum = " +sum);14 }15 }16 }

运行结果:

sum = 100sum =200sum =300sum =400sum =500sum =600

问题解决!

但是,发现,要创建对象,写synchronized代码块是不是有点麻烦呢???我们发现add函数里面的内容都需要同步,就是说add函数里面的代码就是我们要同步的代码,于是直接同步这个函数就可以了,即同步函数:

1 classBank {2 private intsum;3 //Object obj = new Object();

4 public synchronized void add(int num) {//同步函数5 //synchronized(obj) {

6 sum +=num;7 try{8 Thread.sleep(20);9 } catch(InterruptedException e) {10 //TODO Auto-generated catch block

11 e.printStackTrace();12 }13 System.out.println("sum = " +sum);14 //}

15 }16 }

运行结果同上!

十五、同步函数锁的验证

由前面的分析知道,同步是需要一把“锁”,而锁可以是任意类型的对象,那么同步函数到底使用的是什么锁呢??下面来验证。

回到前面售票的程序,改成两个售票员,一个售票员卖票线程放在同步代码块里面,另一个售票员的的线程放在同步函数里面,如果这两个线程用的是同一个锁,那么就不会出现安全隐患。

于是在程序改为下面:

1 packagethread.demo;2 //买票:四个售票员一起卖100张票

3 class Ticket_2 implementsRunnable {4 private int num = 100;5 Object obj = newObject();6 boolean flag = true;7 public voidrun() {8 if(flag)9 {10 while (true)11 {12 synchronized(obj)13 {14 if (num > 0)15 {16 //让线程sleep一会,好让打印语句还没来得及执行后面的打印语句,其他线程17 //就切换进来,这样方便我们观测线程安全隐患

18 try{19 Thread.sleep(20);20 } catch(InterruptedException e) {21 //TODO Auto-generated catch block

22 e.printStackTrace();23 }24 System.out.println(Thread.currentThread().getName() + "...bloc k..." + num--);25 }26 }27 }28

29 }//end if

30 else

31 while (true) show();32 }33

34 public synchronized voidshow()35 {36 if (num > 0)37 {38 //让线程sleep一会,好让打印语句还没来得及执行,其他线程39 //就切换进来,这样方便我们观测线程安全隐患

40 try{41 Thread.sleep(20);42 } catch(InterruptedException e) {43 //TODO Auto-generated catch block

44 e.printStackTrace();45 }46 System.out.println(Thread.currentThread().getName() + "...function..." + num--);47 }48 }49 }50

51 public classSynFunctionLockDemo52 {53

54

55 public static voidmain(String[] args)56 {57

58 Ticket_2 t = newTicket_2();59

60 Thread seller1 = newThread(t);61 Thread seller2 = newThread(t);62

63

64 seller1.start(); //在同步代码块执行

65 t.flag = false; //标志变为false,使得下一个线程在同步函数执行

66 seller2.start();67

68 }69 }

如果是在同步函数里面执行的代码块会打印出带有“function”的字符串。如果是在同步代码块里面执行的会打印出带有"bloc  k"的字符串,执行如下:

Thread-0...function...100Thread-0...function...99Thread-0...function...98Thread-0...function...97Thread-0...function...96Thread-0...function...95Thread-0...function...94Thread-0...function...93Thread-0...function...92Thread-0...function...91Thread-0...function...90Thread-0...function...89Thread-0...function...88Thread-0...function...87Thread-0...function...86Thread-0...function...85Thread-1...function...84Thread-1...function...83Thread-1...function...82Thread-1...function...81Thread-0...function...80Thread-0...function...79Thread-0...function...78Thread-0...function...77Thread-0...function...76Thread-0...function...75Thread-0...function...74Thread-1...function...73Thread-1...function...72Thread-0...function...71Thread-0...function...70Thread-0...function...69Thread-1...function...68Thread-1...function...67Thread-1...function...66Thread-0...function...65Thread-0...function...64Thread-0...function...63Thread-0...function...62Thread-1...function...61Thread-1...function...60Thread-1...function...59Thread-1...function...58Thread-0...function...57Thread-0...function...56Thread-0...function...55Thread-1...function...54Thread-1...function...53Thread-1...function...52Thread-1...function...51Thread-1...function...50Thread-1...function...49Thread-1...function...48Thread-1...function...47Thread-0...function...46Thread-0...function...45Thread-0...function...44Thread-0...function...43Thread-1...function...42Thread-1...function...41Thread-1...function...40Thread-1...function...39Thread-1...function...38Thread-1...function...37Thread-1...function...36Thread-1...function...35Thread-1...function...34Thread-1...function...33Thread-1...function...32Thread-1...function...31Thread-1...function...30Thread-1...function...29Thread-1...function...28Thread-1...function...27Thread-0...function...26Thread-0...function...25Thread-0...function...24Thread-1...function...23Thread-1...function...22Thread-1...function...21Thread-1...function...20Thread-1...function...19Thread-1...function...18Thread-1...function...17Thread-1...function...16Thread-0...function...15Thread-0...function...14Thread-0...function...13Thread-1...function...12Thread-1...function...11Thread-1...function...10Thread-1...function...9Thread-1...function...8Thread-1...function...7Thread-0...function...6Thread-0...function...5Thread-1...function...4Thread-1...function...3Thread-1...function...2Thread-1...function...1

发现竟然都在同步函数里面执行,什么原因呢?原因在于:主线程开启以后一口气运行到了66行代码,注意这里虽然线程0和线程1被开启,即具有了执行资格,但是还不具备执行权,执行权仍然被主线程占有着,等待主线程完事之后,线程0和线程1在真正抢到执行权的时候,发现flag = false,于是全部走进了同步函数!!! 要想使得线程0和线程1分别走进不同的同步方法,需要在开启线程0之后,让主线程停顿一会,让线程0开始执行(此时线程1还没有被开启,是不可能执行的),进入同步代码块,然后等到主线程再执行的时候就会把flag置为false,开启线程1,线程1就可以进入同步函数。

具体改动如下:

1 public static voidmain(String[] args)2 {3

4 Ticket_2 t = newTicket_2();5

6 Thread seller1 = newThread(t);7 Thread seller2 = newThread(t);8

9

10 seller1.start(); //在同步代码块执行11

12 //主线程停顿一会,给线程0执行的机会!!

13 try{14 Thread.sleep(20);15 } catch(InterruptedException e) {16 //TODO Auto-generated catch block

17 e.printStackTrace();18 }19

20 t.flag = false; //标志变为false,使得下一个线程在同步函数执行

21 seller2.start();22

23 }

运行结果:

Thread-0...bloc k...100Thread-0...bloc k...99Thread-1...function...98Thread-0...bloc k...96Thread-1...function...97Thread-0...bloc k...94Thread-1...function...95Thread-0...bloc k...93Thread-1...function...92Thread-0...bloc k...91Thread-1...function...90Thread-1...function...89Thread-0...bloc k...89Thread-0...bloc k...88Thread-1...function...88Thread-0...bloc k...87Thread-1...function...87Thread-1...function...86Thread-0...bloc k...86Thread-1...function...84Thread-0...bloc k...85Thread-1...function...82Thread-0...bloc k...83Thread-0...bloc k...80Thread-1...function...81Thread-0...bloc k...79Thread-1...function...78Thread-0...bloc k...77Thread-1...function...76Thread-1...function...75Thread-0...bloc k...74Thread-1...function...73Thread-0...bloc k...72Thread-1...function...71Thread-0...bloc k...70Thread-1...function...69Thread-0...bloc k...68Thread-1...function...67Thread-0...bloc k...66Thread-1...function...65Thread-0...bloc k...64Thread-1...function...63Thread-0...bloc k...62Thread-1...function...61Thread-0...bloc k...60Thread-1...function...59Thread-0...bloc k...58Thread-1...function...57Thread-0...bloc k...56Thread-1...function...55Thread-0...bloc k...54Thread-1...function...53Thread-0...bloc k...52Thread-1...function...51Thread-0...bloc k...50Thread-1...function...49Thread-0...bloc k...48Thread-1...function...47Thread-0...bloc k...46Thread-1...function...45Thread-0...bloc k...44Thread-1...function...43Thread-0...bloc k...42Thread-1...function...41Thread-0...bloc k...40Thread-1...function...39Thread-0...bloc k...38Thread-1...function...37Thread-0...bloc k...36Thread-1...function...35Thread-0...bloc k...34Thread-1...function...33Thread-0...bloc k...32Thread-1...function...31Thread-0...bloc k...30Thread-1...function...29Thread-0...bloc k...28Thread-1...function...27Thread-0...bloc k...26Thread-1...function...25Thread-0...bloc k...24Thread-1...function...23Thread-0...bloc k...22Thread-1...function...21Thread-0...bloc k...20Thread-1...function...19Thread-0...bloc k...18Thread-0...bloc k...17Thread-1...function...16Thread-1...function...15Thread-0...bloc k...14Thread-1...function...13Thread-0...bloc k...12Thread-1...function...11Thread-0...bloc k...10Thread-1...function...9Thread-0...bloc k...8Thread-1...function...7Thread-0...bloc k...6Thread-1...function...5Thread-0...bloc k...4Thread-1...function...3Thread-0...bloc k...2Thread-1...function...1Thread-0...bloc k...0

可以看到,线程用到了不同的同步方式:同步函数(1线程)和同步代码块(0线程)!但是,也看到89,88,87,86号票被出售了两次,而且还售出了0号票(每次执行情况会不同,但是都会出现类似的线程问题),这说明线程0和线程1没有同步,即二者目前使用的锁不一致,即同步函数使用的不是Object 类型的锁!

我们知道类里面的函数都默认持有this代表着调用该类的对象,同步函数当让持有this啦,试着把同步代码块的锁改为this:

synchronized(this)

然后多次执行,结果如下:

Thread-0...bloc k...100Thread-0...bloc k...99Thread-0...bloc k...98Thread-0...bloc k...97Thread-0...bloc k...96Thread-0...bloc k...95Thread-0...bloc k...94Thread-0...bloc k...93Thread-0...bloc k...92Thread-0...bloc k...91Thread-0...bloc k...90Thread-0...bloc k...89Thread-0...bloc k...88Thread-0...bloc k...87Thread-0...bloc k...86Thread-1...function...85Thread-1...function...84Thread-1...function...83Thread-0...bloc k...82Thread-0...bloc k...81Thread-0...bloc k...80Thread-0...bloc k...79Thread-1...function...78Thread-1...function...77Thread-1...function...76Thread-0...bloc k...75Thread-0...bloc k...74Thread-0...bloc k...73Thread-0...bloc k...72Thread-0...bloc k...71Thread-1...function...70Thread-1...function...69Thread-0...bloc k...68Thread-0...bloc k...67Thread-0...bloc k...66Thread-1...function...65Thread-1...function...64Thread-1...function...63Thread-1...function...62Thread-1...function...61Thread-0...bloc k...60Thread-0...bloc k...59Thread-1...function...58Thread-1...function...57Thread-1...function...56Thread-1...function...55Thread-0...bloc k...54Thread-0...bloc k...53Thread-0...bloc k...52Thread-0...bloc k...51Thread-1...function...50Thread-1...function...49Thread-1...function...48Thread-0...bloc k...47Thread-0...bloc k...46Thread-0...bloc k...45Thread-1...function...44Thread-1...function...43Thread-1...function...42Thread-0...bloc k...41Thread-0...bloc k...40Thread-0...bloc k...39Thread-0...bloc k...38Thread-0...bloc k...37Thread-0...bloc k...36Thread-0...bloc k...35Thread-0...bloc k...34Thread-1...function...33Thread-1...function...32Thread-1...function...31Thread-1...function...30Thread-1...function...29Thread-1...function...28Thread-1...function...27Thread-0...bloc k...26Thread-0...bloc k...25Thread-0...bloc k...24Thread-0...bloc k...23Thread-0...bloc k...22Thread-1...function...21Thread-0...bloc k...20Thread-0...bloc k...19Thread-0...bloc k...18Thread-0...bloc k...17Thread-1...function...16Thread-1...function...15Thread-1...function...14Thread-0...bloc k...13Thread-0...bloc k...12Thread-0...bloc k...11Thread-0...bloc k...10Thread-1...function...9Thread-1...function...8Thread-1...function...7Thread-0...bloc k...6Thread-0...bloc k...5Thread-0...bloc k...4Thread-0...bloc k...3Thread-0...bloc k...2Thread-0...bloc k...1

发现线程问题被解决了!所以验证了同步函数使用的同步锁就是 this!

虽然同步函数书写较为简单(作为同步代码块的简写形式,二者功能一致),但是建议使用同步代码块,因为同步函数使用的锁唯一,而同步代码块可以使用任意对象作为锁,只在需要的若干条语句自由加锁,直观。

但是,注意到,如果需要同步的函数是静态的呢?因为静态函数属于类而不是具体对象,所以静态函数中是不存在this的,所以如果同步函数是静态的,锁显然就不是this!!!

继续按照上面思路验证,在上面程序的基础上,把同步函数改为静态,同时由于静态函数使用了num, 所以把num也改为静态,改动如下:

private static int num = 100;

....public static synchronized void show()

运行如下:

Thread-0...block...100Thread-0...block...99Thread-0...block...98Thread-1...function...97Thread-1...function...96Thread-0...block...95Thread-0...block...94Thread-1...function...93Thread-1...function...92Thread-0...block...91Thread-0...block...89Thread-1...function...90Thread-1...function...88Thread-0...block...87Thread-1...function...86Thread-0...block...86Thread-1...function...85Thread-0...block...85Thread-1...function...84Thread-0...block...83Thread-1...function...82Thread-0...block...81Thread-1...function...80Thread-0...block...80Thread-0...block...79Thread-1...function...78Thread-1...function...77Thread-0...block...77Thread-0...block...76Thread-1...function...75Thread-0...block...74Thread-1...function...73Thread-1...function...72Thread-0...block...72Thread-0...block...71Thread-1...function...71Thread-1...function...70Thread-0...block...69Thread-1...function...68Thread-0...block...67Thread-1...function...66Thread-0...block...65Thread-0...block...64Thread-1...function...63Thread-0...block...62Thread-1...function...61Thread-1...function...60Thread-0...block...59Thread-1...function...58Thread-0...block...57Thread-1...function...56Thread-0...block...55Thread-1...function...53Thread-0...block...54Thread-0...block...52Thread-1...function...52Thread-0...block...51Thread-1...function...50Thread-1...function...49Thread-0...block...48Thread-1...function...47Thread-0...block...46Thread-1...function...45Thread-0...block...44Thread-0...block...43Thread-1...function...42Thread-0...block...41Thread-1...function...40Thread-1...function...39Thread-0...block...38Thread-0...block...37Thread-1...function...36Thread-0...block...35Thread-1...function...34Thread-1...function...33Thread-0...block...32Thread-0...block...31Thread-1...function...30Thread-0...block...29Thread-1...function...28Thread-0...block...27Thread-1...function...26Thread-1...function...25Thread-0...block...24Thread-0...block...23Thread-1...function...22Thread-0...block...21Thread-1...function...20Thread-1...function...19Thread-0...block...18Thread-0...block...17Thread-1...function...16Thread-0...block...15Thread-1...function...14Thread-1...function...13Thread-0...block...12Thread-0...block...11Thread-1...function...10Thread-0...block...9Thread-1...function...8Thread-1...function...7Thread-0...block...6Thread-1...function...5Thread-0...block...4Thread-0...block...3Thread-1...function...2Thread-1...function...1Thread-0...block...0

看到86,85 ,0等处又出现了线程安全问题。这说明,显然静态函数使用的不是锁不是this, 当然静态函数不可能持有this的,与我们预料的一样!

然而每一个类都属于它所在的字节码文件对象,虽然静态函数不属于具体的对象,而是属于一个类,所以静态函数必然持有字节码文件的对象,显然这个对象是静态的!

于是在上面改动的基础上再作如下改动:

synchronized(Ticket_2.class)

... ...

运行结果:

Thread-0...block...100Thread-0...block...99Thread-0...block...98Thread-0...block...97Thread-1...function...96Thread-1...function...95Thread-1...function...94Thread-1...function...93Thread-1...function...92Thread-1...function...91Thread-1...function...90Thread-1...function...89Thread-1...function...88Thread-1...function...87Thread-1...function...86Thread-1...function...85Thread-1...function...84Thread-1...function...83Thread-1...function...82Thread-1...function...81Thread-1...function...80Thread-1...function...79Thread-1...function...78Thread-1...function...77Thread-1...function...76Thread-1...function...75Thread-1...function...74Thread-1...function...73Thread-1...function...72Thread-1...function...71Thread-1...function...70Thread-1...function...69Thread-0...block...68Thread-0...block...67Thread-0...block...66Thread-0...block...65Thread-0...block...64Thread-1...function...63Thread-1...function...62Thread-1...function...61Thread-1...function...60Thread-1...function...59Thread-1...function...58Thread-1...function...57Thread-1...function...56Thread-1...function...55Thread-1...function...54Thread-1...function...53Thread-1...function...52Thread-1...function...51Thread-1...function...50Thread-1...function...49Thread-1...function...48Thread-1...function...47Thread-1...function...46Thread-1...function...45Thread-1...function...44Thread-1...function...43Thread-1...function...42Thread-1...function...41Thread-1...function...40Thread-1...function...39Thread-1...function...38Thread-1...function...37Thread-1...function...36Thread-1...function...35Thread-1...function...34Thread-1...function...33Thread-1...function...32Thread-1...function...31Thread-1...function...30Thread-1...function...29Thread-1...function...28Thread-1...function...27Thread-1...function...26Thread-1...function...25Thread-1...function...24Thread-1...function...23Thread-1...function...22Thread-1...function...21Thread-1...function...20Thread-1...function...19Thread-1...function...18Thread-1...function...17Thread-1...function...16Thread-1...function...15Thread-1...function...14Thread-1...function...13Thread-1...function...12Thread-1...function...11Thread-1...function...10Thread-1...function...9Thread-1...function...8Thread-1...function...7Thread-1...function...6Thread-1...function...5Thread-1...function...4Thread-1...function...3Thread-1...function...2Thread-1...function...1

所以静态的同步函数使用的锁是 该函数所述的字节码文件对象,该对象可以用 getClass方法获取,也可以用当前 类名.class 表示,但是此时由于getClass()是非静态方法,所以只能用类名.class。

十六、单例模式的线程问题的解决方案

1 packagethread.demo;2 //单例模式3 //饿汉式

4 /*

5 class Single6 {7 private static final Single s = new Single();8 private Single(){}9 public static Single getInstance()10 {11 return s;12 }13 }14 */

15

16 //懒汉式

17

18 classSingle_l19 {20 private static Single_l s = null;21 privateSingle_l(){}22 public staticSingle_l getInstance()23 {24 if (s == null)25 {26 synchronized(Single_l.class)27 {28 if (s == null)29 s = newSingle_l();30 }31 }32 returns;33 }34 }35

36

37 public classSingleDemo {38

39 /**

40 *@paramargs41 */

42 public static voidmain(String[] args) {43 //TODO Auto-generated method stub

44

45 }46

47 }

由于懒汉式单例模式的同步代码块的代码有多条,所以可能会出现线程安全问题,所以需要进行同步。

十七、多线程死锁示例

1 packagethread.demo;2 //买票:四个售票员一起卖100张票

3 class Ticket_3 implementsRunnable4 {5 private int num = 100;6 Object obj = newObject();7 boolean flag = true;8 public voidrun()9 {10 if(flag)11 {12 while (true)13 {14 synchronized(obj)15 {16 show();//同步代码块中使用同步函数

17 }18 }19 }//end if

20 else

21 while (true) show();22 }23

24 public synchronized voidshow()25 {26 synchronized(obj)//同步函数中使用同步代码块

27 {28 if (num > 0)29 {30 //让线程sleep一会,好让打印语句还没来得及执行,其他线程31 //就切换进来,这样方便我们观测线程安全隐患

32 try{33 Thread.sleep(10);34 } catch(InterruptedException e) {35 //TODO Auto-generated catch block

36 e.printStackTrace();37 }38 System.out.println(Thread.currentThread().getName() + "...function..." + num--);39 } //end if

40 }//end synchronized

41 }42 }43

44 public classDeadLock45 {46 public static voidmain(String[] args)47 {48

49 Ticket_3 t = newTicket_3();50

51 Thread seller1 = newThread(t);52 Thread seller2 = newThread(t);53

54

55 seller1.start(); //在同步代码块执行56

57 //主线程停顿一会,给线程0执行的机会!!

58 try{59 Thread.sleep(20);60 } catch(InterruptedException e) {61 //TODO Auto-generated catch block

62 e.printStackTrace();63 }64

65 t.flag = false; //标志变为false,使得下一个线程在同步函数执行

66 seller2.start();67

68 }69 }

程序解释:这是一个同步锁嵌套的情况,可以看到在第14行中的同步代码块中使用了同步函数,而在第24行的同步函数中使用了同步代码块,即两个同步锁互相嵌套。下面分析程序的运行:

程序从主函数开始,运行到58行之后,主线程停顿,线程0得到cpu执行权,flag = true, 执行到14 行,线程 0 拿到同步锁 obj, 执行同步代码块,而同步代码块的语句就是调用24行的同步函数,而同步函数里面是26行开始的同步代码块,执行同步代码块,就需要判断锁 obj, 线程0确实持有锁 obj,于是顺利进入26行开始的同步代码块执行,

线程0执行一会,由于代码块中有sleep的存在,主线程有可能切入进来占有cpu执行权,线程0就停顿一会,主线程继续由65行开始执行,flag = false, 线程1开启,然后主线程,0线程,1线程随机切换。

假如刚才停顿的线程0又抢到了cpu执行权,线程0继续执行,再次拿到同步代码块锁obj, 进入到第15行,存在一种情况,就在此时线程1抢到cpu执行权,线程0则持有着代码块的锁obj,并瞅准机会去拿到同步函数的锁this从16行出进入同步函数.

线程1获得cpu执行权之后,由于flag == false, 执行到第21行,拿到同步函数锁this进入同步函数show(), 然后在第26行准备拿锁obj的时候,却发现被obj被线程0占有,只能等待,而线程0即使抢到了cpu执行权,但是同步函数锁this却被线程1占有,也没法继续,于是程序就停顿不前,这就是死锁产生的一个过程!

多次执行上面的代码,会出现类似下面的情况:

1824db8d42606b4fc2df20f3713c70a3.png

即程序陷入了死锁,无法继续运行,所有线程0和线程1都处于等待状态!!

上面的程序只展示了死锁的样子,下面给出一个较为简单的死锁示例备用:

1 packagethread.demo;2 classMyLock3 {4 //“两把锁”

5 public static final Object locka = newObject();6 public static final Object lockb = newObject();7 }8 class Test implementsRunnable9 {10 private booleanflag;11 Test(booleanflag)12 {13 this.flag =flag;14 }15 @Override16 public voidrun()17 {18 if(flag)19 {20 synchronized(MyLock.locka)21 {22 System.out.println("if...loacka");23 synchronized(MyLock.lockb)24 {25 System.out.println("if...loackb");26 }27 }28 }29 else

30 {31 synchronized(MyLock.lockb)32 {33 System.out.println("else...loackb");34 synchronized(MyLock.locka)35 {36 System.out.println("else...loacka");37 }38 }39 }40 }41

42 }43 public classDeadLockTest {44

45 /**

46 *@paramargs47 */

48 public static voidmain(String[] args) {49 Test a = new Test(true);50 Test b = new Test(false);51 Thread t1 = newThread(a);52 Thread t2 = newThread(b);53

54 t1.start();55 t2.start();56 }57

58 }

运行结果:

a6e9999990ca2b4875ea456b6d1181db.png

后续:下一篇博文将会记载多线程间通信的学习笔记。

参考文献:传智播客JAVA SE视频教程,李刚《疯狂JAVA讲义》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值