头条面试题之实现两个线程轮流打印字符串
在面试头条的时候,有一个很有意思的题目,利用两个线程交替打印一个字符串,这里主要就是对多线程中wait/notify的应用,特此记录。
对于wait()和notify()的理解,还是要从jdk官方文档中开始,在Object类方法中有:
void notify() Wakes up a single thread that is waiting on this object’s monitor. 译:唤醒在此对象监视器上等待的单个线程 void notifyAll() Wakes up all threads that are waiting on this object’s monitor. 译:唤醒在此对象监视器上等待的所有线程 void wait( ) Causes the current thread to wait until another thread invokes the notify() method or the notifyAll( ) method for this object. 译:导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法 void wait(long timeout) Causes the current thread to wait until either another thread invokes the notify( ) method or the notifyAll( ) method for this object, or a specified amount of time has elapsed. 译:导致当前的线程等待,直到其他线程调用此对象的notify() 方法或 notifyAll() 方法,或者指定的时间过完。 void wait(long timeout, int nanos) Causes the current thread to wait until another thread invokes the notify( ) method or the notifyAll( ) method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed. 译:导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法,或者其他线程打断了当前线程,或者指定的时间过完。
上面是官方文档的简介,下面我们根据官方文档总结一下:
-
wait( ),notify( ),notifyAll( )都不属于Thread类,而是属于Object基础类,也就是每个对象都有wait( ),notify( ),notifyAll( ) 的功能,因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了。
-
当需要调用以上的方法的时候,一定要对竞争资源进行加锁,如果不加锁的话,则会报 IllegalMonitorStateException 异常
-
当想要调用wait( )进行线程等待时,必须要取得这个锁对象的控制权(对象监视器),一般是放到synchronized(obj)代码中。
-
在while循环里而不是if语句下使用wait,这样,会在线程暂停恢复后都检查wait的条件,并在条件实际上并未改变的情况下处理唤醒通知
-
调用obj.wait( )释放了obj的锁,否则其他线程也无法获得obj的锁,也就无法在synchronized(obj){ obj.notify() } 代码段内唤醒A。
-
notify( )方法只会通知等待队列中的某一个相关线程(不会通知优先级比较高的线程)
-
notifyAll( )通知所有等待该竞争资源的线程(也不会按照线程的优先级来执行)
-
假设有三个线程执行了obj.wait( ),那么obj.notifyAll( )则能全部唤醒thread1,thread2,thread3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,thread1,thread2,thread3只有一个有机会获得锁继续执行,例如thread1,其余的需要等待thread1释放obj锁之后才能继续执行。
-
当调用obj.notify/notifyAll后,调用线程依旧持有obj锁,因此,thread1,thread2,thread3虽被唤醒,但是仍无法获得obj锁。直到调用线程退出synchronized块,释放obj锁后,thread1,thread2,thread3中的一个才有机会获得锁继续执行。
下面针对我们这道题,进行代码解释:
package com.darrenchan; /** * Created by chenchi on 2018/8/15. */ public class ThreadSynchronized { private static String str = "我爱北京天安门"; private static int index; private static boolean flag = true; static class A implements Runnable{ @Override public void run() { synchronized (str){ while(index < str.length()){ if(flag) { System.out.println(Thread.currentThread() +":"+ str.charAt(index)); index++; flag = false; str.notify(); } else { try { str.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } static class B implements Runnable{ @Override public void run() { synchronized (str){ while(index < str.length()){ if(!flag) { System.out.println(Thread.currentThread() +":"+ str.charAt(index)); index++; flag = true; str.notify(); } else { try { str.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } public static void main(String[] args) { new Thread(new A()).start(); new Thread(new B()).start(); } }
一定注意竞态条件即可,运行结果如下:
参考:https://blog.csdn.net/jianiuqi/article/details/53448849
魏印福提供的一种思路如下:
public class Main1 { volatile int x = 0; String string="我爱北京天安门"; Main1() { new Thread(() -> { while (x < string.length()) { while (x % 2 == 0) ; if(x<string.length()){ System.out.println(string.charAt(x)); } x++; } }).start(); new Thread(() -> { while (x < string.length()) { while (x % 2 == 1) ; if(x<string.length()){ System.out.println(string.charAt(x)); } x++; } }).start(); } public static void main(String[] args) { new Main1(); } }