整理总结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();
}
|
|