问题:

按顺序打印字母。如abcabcabcabcabc,即将字母abc按顺序打印5次。

思路:

设置3个线程,分别打印a,b,c,设置1个标志位flag,表示当前该打印哪个字母:

  • 如果线程要打印的字母的标志位和该标志相等,打印字母、更新flag、唤醒等待线程;
  • 否则线程等待,直到被唤醒。

参考代码

/**
 * 按顺序打印字母a,b,c,打印结果为abcabcabcabcabc
 * **/
/**
 * 思路:1)设置3个线程,分别打印字母 a,b,c;
 *      2)控制线程的打印顺序:变量nowFlag:表示正在打印的字母;变量
 *        nextFlag表示下一次该打印哪个字母了(即唤醒哪个线程)。
 *        nowFlag         nextFlag
 *        1 表示要打印a    2 表示下次打印b
 *        2 表示要打印b    3 表示下次打印c
 *        3 表示要打印c    1 表示下次打印a
 *      3)线程打印字母完成后修改变量,并唤醒等待的线程
 * **/
class PrintStr{
   //要打印的字符
   private int flag;
   //循环次数
   private int loopNumber;
   public PrintStr(int flag,int loopNumber){
      this.flag = flag;
      this.loopNumber = loopNumber;
   }
   //打印方法
   public void print(char c, int nowFlag, int nextFlag){
     for(int i = 0; i < loopNumber; i++){
       synchronized(this){
         //当前要打印的字母 不等于 当前线程要打印的字母,线程等待
         //用while,防止虚假唤醒
         while(flag != nowFlag){
            try {
                 this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
         }
         //打印字符
         System.out.print(c);
         //修改变量
         flag = nextFlag;
         //唤醒其他等待线程
         this.notifyAll();
       }
     }
  }
}

public class Print{
  public static void main(String[] args){
    int loopNumber = 5;
    int flag = 1;
    PrintStr p = new PrintStr(flag,loopNumber);//从a开始打印,循环打印5次
    //打印字母a
    new Thread(()->{
       p.print('a',1,2);
    }).start();
    //打印字母b
    new Thread(()->{
       p.print('b',2,3);
    }).start();
    //打印字母c
    new Thread(()->{
       p.print('c',3,1);
    }).start();
  }
}

小结

如果涉及到2个字母,可以用一个boolean类型的flag来标识当前要打印的字母。比如打印ab:flag为true时打印字母a,flag为false时打印b。但是涉及到2个以上的字母就用上面的方法。


利用条件变量解决问题

思路

创建3个条件变量c1、c2、c3,打印字母a、b、c的线程在他们各自对应的条件变量上,开始时3个线程都在自己的条件变量上等待,然后主线程唤醒c1上的线程,打印字母a,打印完成后唤醒下一个条件变量上的线程c2。

参考代码

public class Test{
   public static void main(String[] args)throws InterruptedException{
      Lock lock = new ReentrantLock();
      AwaitSignal a = new AwaitSignal(5, lock);
      //创建条件变量,c1、c2、c3,分别表示打印字母a、b、c的条件变量
      Condition c1 = lock .newCondition();
      Condition c2 = lock .newCondition();
      Condition c3 = lock .newCondition();
      //创建3个线程,分别打印a、b、c
      new Thread(()->{
        a.print('a',c1,c2);
      }).start();
      new Thread(()->{
        a.print('b',c2,c3);
      }).start();
      new Thread(()->{
        a.print('c',c3,c1);
      }).start();
      //先打印字符a
      Thread.sleep(10);
      lock.lock();
      try{
        //唤醒c1		
        c1.signal();
      }cache(Exception e){
        e.printStackTrace();
      }finally{
	lock.unlock();
      }
      
   }
}

class AwaitSignal{
   private int loopNumber;
   private Lock lock;
   public AwaitSignal(int loopNumber, Lock lock){this.loopNumber = loopNumber;this.lock = lock;}
   //current代表 当前条件变量,next 代表 下一个要唤醒的条件变量
   public void print(char c,Condition current,Condition next){
       for(int i = 0; i < loopNumber; i++){
          lock.lock();
          try{
              current.await();
              /** 唤醒后执行的操作 **/
              //1.打印字符
              System.out.print(c);
              //2.唤醒其他线程
              next.signal();
          }cache(Exception e){
             e.printStackTrace();
          }finally{
            lock.unlock();
          }
       }
   }
}

小结

在加锁时的代码形式:

lock();
 try{…}
 cache(Exception e){ e.printStackTrace();}
 finally{ unlock(); }

利用park/unpark解决问题

参考代码

public class Test{
  static Thread t1;
  static Thread t2;
  static Thread t3;
  public static void main(String[] args){
    Print p = new Print(5);
    t1 = new Thread(()->{
       p.print('a',t2);
    });
    t2 = new Thread(()->{
       p.print('b',t3);
    });
    t3 = new Thread(()->{
       p.print('c',t1);
    });
    t1.start();
    t2.start();
    t3.start();
    //唤醒线程1
    LockSupport.unpark(t1);
  }
}

class Print{
  private int loopNumber;
  public Print(int loopNumber){this.loopNumber = loopNumber;}
  public void print(char c,Thread next){
    for(int i = 0; i < loopNumber; i++){
       //线程等待
       LockSupport.park();
       //打印字母
       System.out.print(c);
       //唤醒下一个线程
       LockSupport.unpark(next);
    }
  }
}