java object 类wait() notify() 以及Thread类 join() yile学习

Thread类的join()

join是Thread类的一个方法,启动线程后直接调用,例如:

1
2
3
Thread mThread = new AThread(); 
mThread.start();
mThread.join();

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
在JDk的API里对于join()方法是:

public final void join() throws InterruptedException Waits for this thread to die. Throws: InterruptedException - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.

即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

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
public class ThreadTest {

public static void main(String[] args) {
// TODO Auto-generated method stub
Integer num = new Integer(0);
Thread mAthread = new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
synchronized (num) {
for (int i = 0; i < 10; i++) {
System.out.println("thread:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

});
Thread mBthread = null;
mBthread = new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
if(!mBthread.holdsLock(num)){
System.out.println("thread B holdsLock");
}
synchronized (num) {
for (int i = 0; i < 10; i++) {
System.out.println("thread B:" + i);
}
try {
mAthread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("thread B finish");
}
}

});
mAthread.start();
mBthread.start();
try {
mAthread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

System.out.println("finish");
}

进入看一下它的JDK源码:

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
public final void join() throws InterruptedException {
join(0L);
}

public final synchronized void join(long l)
throws InterruptedException
{

long l1 = System.currentTimeMillis();
long l2 = 0L;
if(l < 0L)
throw new IllegalArgumentException("timeout value is negative");
if(l == 0L)
for(; isAlive(); wait(0L));
else
do
{
if(!isAlive())
break;
long l3 = l - l2;
if(l3 <= 0L)
break;
wait(l3);
l2 = System.currentTimeMillis() - l1;
} while(true);
}

单纯从代码上看: 如果线程被生成了,但还未被起动,isAlive()将返回false,调用它的join()方法是没有作用的。将直接继续向下执行。 在AThread类中的run方法中,bt.join()是判断bt的active状态,如果bt的isActive()方法返回false,在bt.join(),这一点就不用阻塞了,可以继续向下进行了。从源码里看,wait方法中有参数,也就是不用唤醒谁,只是不再执行wait,向下继续执行而已。 * 在join()方法中,对于isAlive()和wait()方法的作用对象是个比较让人困惑的问题:

isAlive()方法的签名是:public final native boolean isAlive(),也就是说isAlive()是判断当前线程的状态,也就是bt的状态。
wait()方法在jdk文档中的解释如下:

Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).The current thread must own this object’s monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object’s monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.

java object 类wait() notify()

如果需要在线程间相互唤醒的话就需要借助Object.wait(), Object.nofity()了。
Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:

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
public class ThreadTest2 implements Runnable {

private String mName;
private Object mPre;
private Object mSelf;

public ThreadTest2(String name,Object pre,Object self){
mName = name;
mPre = pre;
mSelf = self;
}


public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Object mA = new Object();
Object mB = new Object();
Object mC = new Object();
ThreadTest2 mAThread = new ThreadTest2("A",mC,mA);
ThreadTest2 mBThread = new ThreadTest2("B",mA,mB);
ThreadTest2 mCThread = new ThreadTest2("C",mB,mC);

new Thread(mAThread).start();
new Thread(mBThread).start();
new Thread(mCThread).start();
System.out.println("main thread finish");
}

@Override
public void run() {
// TODO Auto-generated method stub
int count = 10;
while(count > 0){
synchronized(mPre){
synchronized(mSelf){
System.out.println(mName);
count --;
mSelf.notify();
}
try {
mPre.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

}

先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。具体来说就是,在main主线程启动ThreadA后,需要在ThreadA执行完,在prev.wait()等待时,再切回线程启动ThreadB,ThreadB执行完,在prev.wait()等待时,再切回主线程,启动ThreadC,只有JVM按照这个线程运行顺序执行,才能保证输出的结果是正确的。而这依赖于JVM的具体实现。考虑一种情况,如下:如果主线程在启动A后,执行A,过程中又切回主线程,启动了ThreadB,ThreadC,之后,由于A线程尚未释放self.notify,也就是B需要在synchronized(prev)处等待,而这时C却调用synchronized(prev)获取了对b的对象锁。这样,在A调用完后,同时ThreadB获取了prev也就是a的对象锁,ThreadC的执行条件就已经满足了,会打印C,之后释放c,及b的对象锁,这时ThreadB具备了运行条件,会打印B,也就是循环变成了ACBACB了。这种情况,可以通过在run中主动释放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
@Override
public void run() {
// TODO Auto-generated method stub
int count = 10;
while(count > 0){
synchronized(mPre){
synchronized(mSelf){
System.out.println(mName);
count --;
try{
Thread.sleep(1);
}
catch (InterruptedException e){
e.printStackTrace();
}
mSelf.notify();

}
try {
mPre.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

运行后的打印结果就变成了ACBACB了。为了避免这种与JVM调度有关的不确定性。需要让A,B,C三个线程以确定的顺序启动,最终代码如下:

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
 public class ThreadTest2 implements Runnable {

private String mName;
private Object mPre;
private Object mSelf;

public ThreadTest2(String name,Object pre,Object self){
mName = name;
mPre = pre;
mSelf = self;
}


public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Object mA = new Object();
Object mB = new Object();
Object mC = new Object();
ThreadTest2 mAThread = new ThreadTest2("A",mC,mA);
ThreadTest2 mBThread = new ThreadTest2("B",mA,mB);
ThreadTest2 mCThread = new ThreadTest2("C",mB,mC);

new Thread(mAThread).start();
Thread.sleep(10);
new Thread(mBThread).start();
Thread.sleep(10);
new Thread(mCThread).start();
Thread.sleep(10);
System.out.println("main thread finish");
}

@Override
public void run() {
// TODO Auto-generated method stub
int count = 10;
while(count > 0){
synchronized(mPre){
synchronized(mSelf){
System.out.println(mName);
count --;
mSelf.notify();
}
try {
mPre.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

}

文章目录
  1. 1. Thread类的join()
  2. 2. java object 类wait() notify()
,