线程协作
生产者/消费者模式是一个经典的线程同步以及通信的模型。
假设有这样一种情况,有一个盘子,盘子里只能放一个鸡蛋,A线程专门往盘子里放鸡蛋,如果盘子里有鸡蛋,则一直等到盘子里没鸡蛋,B线程专门从盘子里取鸡蛋,如果盘子里没鸡蛋,则一直等到盘子里有鸡蛋。这里盘子是一个互斥区,每次放鸡蛋是互斥的,每次取鸡蛋也是互斥的,A线程放鸡蛋,如果这时B线程要取鸡蛋,由于A没有释放锁,B线程处于等待状态,进入阻塞队列,放鸡蛋之后,要通知B线程取鸡蛋,B线程进入就绪队列,反过来,B线程取鸡蛋,如果A线程要放鸡蛋,由于B线程没有释放锁,A线程处于等待状态,进入阻塞队列,取鸡蛋之后,要通知A线程放鸡蛋,A线程进入就绪队列。我们希望当盘子里有鸡蛋时,A线程阻塞,B线程就绪,盘子里没鸡蛋时,A线程就绪,B线程阻塞,代码如下:
- importjava.util.ArrayList;
- importjava.util.List;
- /**定义一个盘子类,可以放鸡蛋和取鸡蛋*/
- publicclassPlate{
- /**装鸡蛋的盘子*/
- List<Object>eggs=newArrayList<Object>();
- /**取鸡蛋*/
- publicsynchronizedObjectgetEgg(){
- while(eggs.size()==0){
- try{
- wait();
- }catch(InterruptedExceptione){
- e.printStackTrace();
- }
- }
- Objectegg=eggs.get(0);
- eggs.clear();//清空盘子
- notify();//唤醒阻塞队列的某线程到就绪队列
- System.out.println("拿到鸡蛋");
- returnegg;
- }
- /**放鸡蛋*/
- publicsynchronizedvoidputEgg(Objectegg){
- while(eggs.size()>0){
- try{
- wait();
- }catch(InterruptedExceptione){
- e.printStackTrace();
- }
- }
- eggs.add(egg);//往盘子里放鸡蛋
- notify();//唤醒阻塞队列的某线程到就绪队列
- System.out.println("放入鸡蛋");
- }
- staticclassAddThreadextendsThread{
- privatePlateplate;
- privateObjectegg=newObject();
- publicAddThread(Plateplate){
- this.plate=plate;
- }
- publicvoidrun(){
- plate.putEgg(egg);
- }
- }
- staticclassGetThreadextendsThread{
- privatePlateplate;
- publicGetThread(Plateplate){
- this.plate=plate;
- }
- publicvoidrun(){
- plate.getEgg();
- }
- }
- publicstaticvoidmain(Stringargs[]){
- Plateplate=newPlate();
- for(inti=0;i<10;i++){
- newThread(newAddThread(plate)).start();
- newThread(newGetThread(plate)).start();
- }
- }
- }
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
下面讲述了一个线程通信的,在此也分享一下。
题目:子线程循环10次,主线程循环100次,如此循环100次。
- publicclassThreadTest2{
- publicstaticvoidmain(String[]args){
- finalBusinessbusiness=newBusiness();
- newThread(newRunnable(){
- @Override
- publicvoidrun(){
- threadExecute(business,"sub");
- }
- }).start();
- threadExecute(business,"main");
- }
- publicstaticvoidthreadExecute(Businessbusiness,StringthreadType){
- for(inti=0;i<100;i++){
- try{
- if("main".equals(threadType)){
- business.main(i);
- }else{
- business.sub(i);
- }
- }catch(InterruptedExceptione){
- e.printStackTrace();
- }
- }
- }
- }
- classBusiness{
- privatebooleanbool=true;
- publicsynchronizedvoidmain(intloop)throwsInterruptedException{
- while(bool){
- this.wait();
- }
- for(inti=0;i<100;i++){
- System.out.println("mainthreadseqof"+i+",loopof"+loop);
- }
- bool=true;
- this.notify();
- }
- publicsynchronizedvoidsub(intloop)throwsInterruptedException{
- while(!bool){
- this.wait();
- }
- for(inti=0;i<10;i++){
- System.out.println("subthreadseqof"+i+",loopof"+loop);
- }
- bool=false;
- this.notify();
- }
- }
大家注意到没有,在调用wait方法时,都是用while判断条件的,而不是if,在wait方法说明中,也推荐使用while,因为在某些特定的情况下,线程有可能被假唤醒,使用while会循环检测更稳妥。
Timer和TimerTask可以做为实现线程的第三种方式,前两中方式分别是继承自Thread类和实现Runnable接口。
Timer是一种线程设施,用于安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行,可以看成一个定时器,可以调度TimerTask。TimerTask是一个抽象类,实现了Runnable接口,所以具备了多线程的能力。
一个Timer可以调度任意多个TimerTask,它会将TimerTask存储在一个队列中,顺序调度,如果想两个TimerTask并发执行,则需要创建两个Timer。下面来看一个简单的例子:
- importjava.util.Timer;
- importjava.util.TimerTask;
- publicclassTimerTest{
- staticclassMyTimerTask1extendsTimerTask{
- publicvoidrun(){
- System.out.println("爆炸!!!");
- }
- }
- publicstaticvoidmain(String[]args){
- Timertimer=newTimer();
- timer.schedule(newMyTimerTask1(),2000);//两秒后启动任务
- }
- }
schedule是Timer调度任务的方法,Timer重构了四个schedule方法,具体可以查看JDK API。
看一个稍复杂的例子,假设有这样一种需求,实现一个连环,2秒后爆炸一次,3秒后爆炸一次,如此循环下去,这就需要创建两个任务,互相调度,代码如下:
- importjava.util.Date;
- importjava.util.Timer;
- importjava.util.TimerTask;
- publicclassTimerTest{
- staticclassMyTimerTask1extendsTimerTask{
- publicvoidrun(){
- System.out.println("爆炸!!!");
- newTimer().schedule(newMyTimerTask2(),2000);
- }
- }
- staticclassMyTimerTask2extendsTimerTask{
- publicvoidrun(){
- System.out.println("爆炸!!!");
- newTimer().schedule(newMyTimerTask1(),3000);
- }
- }
- publicstaticvoidmain(String[]args){
- Timertimer=newTimer();
- timer.schedule(newMyTimerTask2(),2000);
- while(true){
- System.out.println(newDate().getSeconds());
- try{
- Thread.sleep(1000);
- }catch(InterruptedExceptione){
- //TODOAuto-generatedcatchblock
- e.printStackTrace();
- }
- }
- }
- }