配色: 字号:
整理总结Java多线程程序编写的要点
2016-12-21 | 阅:  转:  |  分享 
  
整理总结Java多线程程序编写的要点

这篇文章主要介绍了Java多线程程序编写的要点,包括线程的状态控制和优先级以及线程的通信问题等方面,非常之全面!需要的朋友可以参考下

线程状态图





线程共包括以下5种状态。

1.新建状态(New):线程对象被创建后,就进入了新建状态。例如,Threadthread=newThread()。

2.就绪状态(Runnable):也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。

3.运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。

4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

(01)等待阻塞--通过调用线程的wait()方法,让线程等待某工作的完成。

(02)同步阻塞--线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。

(03)其他阻塞--通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

实现多线程的方式Thread和RunnableThread:继承thread类,实现run方法,在main函数中调用start方法启动线程

Runnable:接口,实现Runnable接口,作为参数传递给Thread的构造函数,在main中调用start方法

例子:

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 classtaskimplementsRunnable{

privateintticket=10;

@Override

publicvoidrun(){

for(inti=0;i<20;i++){

if(this.ticket>0){

System.out.println(Thread.currentThread().getName()

+"soldticket"+this.ticket--);

}

}

}

};

publicclassRunnableTest{

publicstaticvoidmain(String[]args){

taskmytask=newtask();

Threadt1=newThread(mytask);

Threadt2=newThread(mytask);

Threadt3=newThread(mytask);

t1.start();

t2.start();

t3.start();

}

}

//ThreadTest.java源码

classMyThreadextendsThread{

privateintticket=10;

publicvoidrun(){

for(inti=0;i<20;i++){

if(this.ticket>0){

System.out.println(this.getName()+"卖票:ticket"

+this.ticket--);

}

}

}

}

publicclassThreadTest{

publicstaticvoidmain(String[]args){

//启动3个线程t1,t2,t3;每个线程各卖10张票!

MyThreadt1=newMyThread();

MyThreadt2=newMyThread();

MyThreadt3=newMyThread();

t1.start();

t2.start();

t3.start();

}

}; Thread与Runnable的区别

Thread是类,而Runnable是接口;Thread本身是实现了Runnable接口的类。我们知道“一个类只能有一个父类,但是却能实现多个接口”,因此Runnable具有更好的扩展性。此外,Runnable还可以用于“资源的共享”。即,多个线程都是基于某一个Runnable对象建立的,它们会共享Runnable对象上的资源。通常,建议通过“Runnable”实现多线程!

Thread的run与start

start():它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。start()实际上是通过本地方法start0()启动线程的。而start0()会新运行一个线程,新线程会调用run()方法。

run():run()就和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程!run()就是直接调用Thread线程的Runnable成员的run()方法,并不会新建一个线程。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22 //Demo.java的源码

classMyThreadextendsThread{

publicMyThread(Stringname){

super(name);

}

publicvoidrun(){

System.out.println(Thread.currentThread().getName()+"isrunning");

}

};

publicclassDemo{

publicstaticwww.visa158.commain(String[]args){

Threadmythread=newMyThread("mythread");

System.out.println(Thread.currentThread().getName()+"callmythread.run()");

mythread.run();

System.out.println(Thread.currentThread().getName()+"callmythread.start()");

mythread.start();

}

} 输出:

1

2

3

4 maincallmythread.run()

mainisrunning

maincallmythread.start()

mythreadisrunning synchronized在java中每个对象都有一个同步锁,当我们调用对象的synchronized方法就获取了对象锁,synchronized(obj)就获取了“obj这个对象”的同步锁.不同线程对同步锁的访问是互斥的.某时间点对象的同步锁只能被一个线程获取到.通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问.例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁”——线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。

基本规则

第一条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

第二条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。

第三条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

synchronized方法

1

2

3

4

5

6

7

8

9

10 publicsynchronizedvoidfoo1(){

System.out.println("synchronizedmethoed");

}

synchronized代码块

publicvoidfoo2(){

synchronized(this){

System.out.println("synchronizedmethoed");

}

} 1

2

3

4

5

6 pulbicclassSomething{

publicsynchronizedvoidisSyncA(){}

publicsynchronizedvoidisSyncB(){}

publicstaticsynchronizedvoidcSyncA(){}

publicstaticsynchronizedvoidcSyncB(){}

} (01)x.isSyncA()与x.isSyncB()不能被同时访问。因为isSyncA()和isSyncB()都是访问同一个对象(对象x)的同步锁!

(02)x.isSyncA()与y.isSyncA()可以同时被访问。因为访问的不是同一个对象的同步锁,x.isSyncA()访问的是x的同步锁,而y.isSyncA()访问的是y的同步锁。

(03)x.cSyncA()与y.cSyncB()不能被同时访问。因为cSyncA()和cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA(),y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时反问。

(04)x.isSyncA()与Something.cSyncA()可以被同时访问。因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。

线程阻塞与唤醒wait,notify,notifyAll在Object.java中,定义了wait(),notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

Object类中关于等待/唤醒的API详细信息如下:

notify()--唤醒在此对象监视器上等待的单个线程。

notifyAll()--唤醒在此对象监视器上等待的所有线程。

wait()--让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或notifyAll()方法”,当前线程被唤醒(进入“就绪状态”)。

wait(longtimeout)--让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或notifyAll()方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。

wait(longtimeout,intnanos)--让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

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 //WaitTest.java的源码

classThreadAextendsThread{

publicThreadA(Stringname){

super(name);

}

publicvoidrun(){

synchronized(this){

System.out.println(Thrwww.hunanwang.netcurrentThread().getName()+"callnotify()");

//唤醒当前的wait线程

notify();

}

}

}

publicclassWaitTest{

publicstaticvoidmain(String[]args){

ThreadAt1=newThreadA("t1");

synchronized(t1){

try{

//启动“线程t1”

System.out.println(Thread.currentThread().getName()+"startt1");

t1.start();

//主线程等待t1通过notify()唤醒。

System.out.println(Thread.currentThread().getName()+"wait()");

t1.wait();

System.out.println(Thread.currentThread().getName()+"continue");

}catch(InterruptedExceptione){

e.printStackTrace();

}

}

}

} 输出

1

2

3

4 mainstartt1

mainwait()

t1callnotify()

maincontinue (01)注意,图中"主线程"代表“主线程main”。"线程t1"代表WaitTest中启动的“线程t1”。而“锁”代表“t1这个对象的同步锁”。

(02)“主线程”通过newThreadA("t1")新建“线程t1”。随后通过synchronized(t1)获取“t1对象的同步锁”。然后调用t1.start()启动“线程t1”。

(03)“主线程”执行t1.wait()释放“t1对象的锁”并且进入“等待(阻塞)状态”。等待t1对象上的线程通过notify()或notifyAll()将其唤醒。

(04)“线程t1”运行之后,通过synchronized(this)获取“当前对象的锁”;接着调用notify()唤醒“当前对象上的等待线程”,也就是唤醒“主线程”。

(05)“线程t1”运行完毕之后,释放“当前对象的锁”。紧接着,“主线程”获取“t1对象的锁”,然后接着运行。

t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在“主线程main”中。而主线程必须是“当前线程”,也就是运行状态,才可以执行t1.wait()。所以,此时的“当前线程”是“主线程main”!因此,t1.wait()是让“主线程”等待,而不是“线程t1”!

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 packagethread.Test;

publicclassNotifyAllTest{

privatestaticObjectobj=newObject();

publicstaticvoidmain(String[]args){

ThreadAt1=newThreadA("t1");

ThreadAt2=newThreadA("t2");

ThreadAt3=newThreadA("t3");

t1.start();

t2.start();

t3.start();

try{

System.out.println(Thread.currentThread().getName()+"sleep(3000)");

Thread.sleep(3000);

}catch(InterruptedExceptione){

e.printStackTrace();

}

synchronized(obj){

System.out.println(Thread.currentThread().getName()+"notifyAll()");

obj.notifyAll();//在此唤醒t1.t2.t3

}

}

staticclassThreadAextendsThread{

publicThreadA(Stringname){

super(name);

}

publicvoidrun(){

synchronized(obj){

try{

//打印输出结果

System.out.println(Thread.currentThread().getName()+"wait");

//释放obj对象锁

obj.wait();

//打印输出结果

System.out.println(Thread.currentThread().getName()+"continue");

}catch(InterruptedExceptione){

e.printStackTrace();

}

}

}

}

} 输出:

1

2

3

4

5

6

7

8 t1wait

mainsleep(3000)

t3wait

t2wait

mainnotifyAll()

t2continue

t3continue

t1continue (01)主线程中新建并且启动了3个线程"t1","t2"和"t3"。

(02)主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的过程中,我们假设"t1","t2"和"t3"这3个线程都运行了。以"t1"为例,当它运行的时候,它会执行obj.wait()等待其它线程通过notify()或额nofityAll()来唤醒它;相同的道理,"t2"和"t3"也会等待其它线程通过nofity()或nofityAll()来唤醒它们。

(03)主线程休眠3秒之后,接着运行。执行obj.notifyAll()唤醒obj上的等待线程,即唤醒"t1","t2"和"t3"这3个线程。紧接着,主线程的synchronized(obj)运行完毕之后,主线程释放“obj锁”。这样,"t1","t2"和"t3"就可以获取“obj锁”而继续运行了!

notify,notifyall与锁的关系

Object中的wait(),notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。

wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!

OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。

负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。

总之,notify(),wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(),wait()等函数定义在Object类,而不是Thread类中的原因。

线程让步yield线程让步,使线程从执行状态变为就绪状态,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行。

yield与wait

(01)wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而不yield()是让线程由“运行状态”进入到“就绪状态”。

(02)wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。

(03)wait是object的方法,yield是Thread的方法

线程休眠sleepsleep()的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。

sleep与wait的区别

wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而sleep()的作用是也是让当前线程由“运行状态”进入到“休眠(阻塞)状态。(这个其实区别不大)

wait()会释放对象的同步锁,而sleep()则不会释放锁

wait是object的方法,sleep是Thread的方法

join让主线程等待,子线程运行完毕,主线程才能继续运行

interrupt用来终止处于阻塞状态的线程

1

2

3

4

5

6

7

8

9

10 @Override

publicvoidrun(){

try{

while(true){

//执行任务...

}

}catch(InterruptedExceptionie){

//由于产生InterruptedException异常,退出while(true)循环,线程终止!

}

} 1

2

3

4

5

6 @Override

publicvoidrun(){

while(!isInterrupted()){

//执行任务...

}

} 1

2

3

4

5

6

7

8

9

10

11 @Override

publicvoidrun(){

try{

//1.isInterrupted()保证,只要中断标记为true就终止线程。

while(!isInterrupted()){

//执行任务...

}

}catch(InterruptedExceptionie){

//2.InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。

}

}

如果一个变量是volatile类型,则对该变量的读写就将具有原子性。如果是多个volatile操作或类似于volatile++这种复合操作,这些操作整体上不具有原子性。

volatile变量自身具有下列特性:

[可见性]:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。

[原子性]:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

volatile写:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。

volatile读:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

消息传递:消息的发送在消息的接受之前,同步隐式进行。

ThreadLocal

ThreadLocal不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set()到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new对象的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。

ThreadLocal的一个实现

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 importjava.util.Collections;

importjava.util.HashMap;

importjava.util.Map;

/

使用了ThreadLocal的类



@authorleizhimin2010-1-510:35:27

/

publicclassMyThreadLocal{

//定义了一个ThreadLocal变量,用来保存int或Integer数据

privatecom.lavasoft.test2.ThreadLocaltl=newcom.lavasoft.test2.ThreadLocal(){

@Override

protectedIntegerinitialValue(){

return0;

}

};

publicIntegergetNextNum(){

//将tl的值获取后加1,并更新设置t1的值

tl.set(tl.get()+1);

returntl.get();

}

}

classThreadLocal{

privateMapmap=Collections.synchronizedMap(newHashMap());

publicThreadLocal(){

}

protectedTinitialValue(){

returnnull;

}

publicTget(){

Threadt=Thread.currentThread();

Tobj=map.get(t);

if(obj==null&&!map.containsKey(t)){

obj=initialValue();

map.put(t,obj);

}

returnobj;

}

publicvoidset(Tvalue){

map.put(Thread.currentThread(),value);

}

publicvoidremove(){

map.remove(Thread.currentThread());

}

} 事实上ThreadLocal是这样做的:

1

2

3

4

5

6

7

8

9

10 publicTget(){

Threadt=Thread.currentThread();

ThreadLocalMapmap=getMap(t);

if(map!=null){

ThreadLocalMap.Entrye=map.getEntry(this);

if(e!=null)

return(T)e.value;

}

returnsetInitialValue();

}





















献花(0)
+1
(本文系白狐一梦首藏)