@@ -125,10 +125,164 @@ end notify() ThreadName=Thread-2 time=1484302438110
125125```
126126测试方法,首先调用上wait的例子,让ServiceThread线程进入等待状态,然后执行2个含有notify操作的线程,可以看出,第一个notify执行完,wait线程并没有立即开始运行,而是Thread-1继续执行后续的notify方法,直到同步语句块结束,然后wait线程立即得到锁,并继续运行。之后Thread-2开始运行,直到结束,因为已经没有等待的线程,所以不会有后续的等待的线程运行。
127127这里,可以看出一个细节,竞争锁的线程有3个,一个包含wait线程,两个包含notify线程。第一个notify执行结束,获得锁一定是阻塞的线程,而不是另一个notify的线程。
128+ 上面的程序展现了等待/通知机制是如何通过wait和notify实现。在这里,我们可以看出wait方法使线程进入等待,和` Thread.sleep ` 是很相似的。但是两者却截然不同,区别如下:
129+ - wait使线程进入等待,是可以被通知唤醒的,但是sleep只能自己到时间唤醒。
130+ - wait方法是对象锁调用的成员方法,而sleep却是Thread类的静态方法
131+ - wait方法出现在同步方法或者同步代码块中,但是sleep方法可以出现在非同步代码中。
128132
133+ wait和notify还提供了几个其他API,如` wait(long timeout) ` 该方法可以提供一个唤醒的时间,如果在时间内,没有其他线程唤醒该等待线程,则到设定的时间,会自动结束等待。
134+ 因为notify仅仅能唤醒一个线程,所以Java提供了一个` notifyAll() ` 的方法来唤醒所有的线程,让所有的线程来竞争。我们看一下只唤醒一个线程和唤醒所有线程的不同。
135+ ```
136+ public class CommonWait {
137+
138+ private Object object;
139+ public CommonWait(Object object){
140+ this.object = object;
141+ }
142+
143+ public void doSomething() throws Exception{
144+ synchronized (object){
145+ System.out.println("begin wait " + Thread.currentThread().getName());
146+ object.wait();
147+ System.out.println("end wait " + Thread.currentThread().getName());
148+ }
149+ }
150+ }
151+ ```
152+ ```
153+ public class CommonNotify {
154+
155+ private Object object;
156+ public CommonNotify(Object object){
157+ this.object = object;
158+ }
159+
160+ public void doNotify(){
161+ synchronized (object){
162+ System.out.println("准备通知");
163+ object.notify();
164+ System.out.println("通知结束");
165+ }
166+ }
167+ }
168+ ```
169+ 测试通知一个等待线程
170+ ```
171+ public void testRun() throws Exception{
172+ Object lock = new Object();
173+ new Thread(()->{
174+ try {
175+ new CommonWait(lock).doSomething();
176+ } catch (Exception e) {
177+ e.printStackTrace();
178+ }
179+ }).start();
180+
181+ new Thread(()->{
182+ try {
183+ new CommonWait(lock).doSomething();
184+ } catch (Exception e) {
185+ e.printStackTrace();
186+ }
187+ }).start();
188+
189+ Thread.sleep(1000);
190+
191+ new Thread(()->{
192+ new CommonNotify(lock).doNotify();
193+ }).start();
194+
195+ Thread.sleep(1000 * 3);
196+
197+ }
198+ ```
199+ 结果如下:
200+ ```
201+ begin wait Thread-0
202+ begin wait Thread-1
203+ 准备通知
204+ 通知结束
205+ end wait Thread-0
206+ ```
207+ 结果看来,只有一个线程结束了等待,继续往下面执行。另一个线程直到结束也没有执行。
208+ 现在看一下notifyAll的效果,把` CommonNotify ` 这个类中的` object.notify(); ` 改成` object.notifyAll() `
209+ 其他的不变,看看结果:
210+ ```
211+ begin wait Thread-0
212+ begin wait Thread-1
213+ 准备通知
214+ 通知结束
215+ end wait Thread-1
216+ end wait Thread-0
217+ ```
218+ 很明显,两个等待线程都执行了,而且这次Thread-1的线程先执行,可见通知唤醒是随机的。
219+ 这里详细说一下,这个结果。wait使线程进入了阻塞状态,阻塞状态可以细分为3种:
220+ - 等待阻塞:运行的线程执行wait方法,JVM会把该线程放入等待队列中。
221+ - 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池当中。
222+ - 其他阻塞: 运行的线程执行了` Thread.sleep ` 或者` join ` 方法,或者发出I/O请求时,JVM会把该线程置为阻塞状态。当` sleep() ` 状态超时、join()等待线程终止,或者超时、或者I/O处理完毕时,线程重新转入可运行状态。
223+
224+ 可运行状态就是线程执行` start ` 时,就是可运行状态,一旦CPU切换到这个线程就开始执行里面的run方法就进入了运行状态。
225+ 上面会出现这个结果,就是因为notify仅仅让一个线程进入了可运行状态,而另一个线程则还在阻塞中。而` notifyAll ` 则使所有的线程都从等待队列中出来,而因为同步代码的关系,获得锁的线程进入可运行态,没有得到锁的则进入锁池,也是阻塞状态,但是会因为锁的释放而重新进入可运行态。所以notifyAll会让所有wait的线程都会继续执行。
226+
227+ ## join方法的使用
228+ wait方法使线程进入阻塞,并且因为通知而唤醒执行,sleep方法同样使线程进入阻塞,并且因此超时而结束阻塞。以上两者都是因为特定的条件而结束阻塞,现在主线程需要知道子线程的结果再继续执行,这个时候要怎么做,用通知/等待不是很好适用于这个情况,sleep则完全不知道要等待的时间。因此Java提供了一个` join() ` 方法,` join() ` 方法是Thread对象的方法,他的功能是使所属的线程对象x正常执行run方法的内容,而使当前线程z进行无限期的阻塞,等待线程x销毁后在继续执行线程z后面的代码。这说起来有点绕口,其实看例子就很简单。
229+ ```
230+ public class JoinThread extends Thread{
231+ @Override
232+ public void run() {
233+ super.run();
234+ try{
235+ int secondValue = (int)(Math.random() * 10000);
236+ System.out.println(secondValue);
237+ Thread.sleep(secondValue);
238+ }catch (InterruptedException e){
239+ e.printStackTrace();
240+ }
241+ }
242+ }
243+ ```
244+ 其测试的方法如下:
245+ ```
246+ public void testRun() throws Exception {
247+ JoinThread joinThread = new JoinThread();
248+ joinThread.start();
249+ joinThread.join();
250+ System.out.println("我想当Join对象执行完毕后我再执行,我做到了");
129251
252+ }
253+ ```
254+ 结果如下:
255+ ```
256+ 3519
257+ 我想当Join对象执行完毕后我再执行,我做到了
258+ ```
259+ 看上去join方法很神奇,可以实现线程在执行上面的次序。但是实际上join方法内部是通过wait实现的。
260+ ```
261+ public final synchronized void join(long millis)
262+ throws InterruptedException {
263+ long base = System.currentTimeMillis();
264+ long now = 0;
130265
266+ if (millis < 0) {
267+ throw new IllegalArgumentException("timeout value is negative");
268+ }
131269
270+ if (millis == 0) {
271+ while (isAlive()) {
272+ wait(0);
273+ }
274+ } else {
275+ while (isAlive()) {
276+ long delay = millis - now;
277+ if (delay <= 0) {
278+ break;
279+ }
280+ wait(delay);
281+ now = System.currentTimeMillis() - base;
282+ }
283+ }
284+ }
285+ ```
132286
133287
134288
0 commit comments