采访:如何确保线程在另一个线程之后运行?

25

有线程T1T2T3,我们如何确保线程T2T1之后运行,线程T3T2之后运行?

这个问题是我面试中被问到的,我没有回答。请详细解释一下。


11
回答:不要试图使用分离的线程去执行显然是连续的任务。线程的重点是概念上同时执行两件事情。如果一个线程必须在另一个线程开始之前完成,那么它们就没有必要成为单独的线程;把它们作为单独的线程并试图在它们上面强制执行序列化只会增加复杂性而无法得到任何好处。 - cHao
18个回答

30
这将是最简单、最笨的方法:
final Thread t1 = new Thread(new T1()); // assume T1 is a Runnable
t1.start();
t1.join();
final Thread t2 = new Thread(new T2());
t2.start();
t2.join();
final Thread t3 = new Thread(new T3());
t3.start();
t3.join();

4
来吧,你还抱怨我按顺序运行我的可运行程序!你至少可以让它们有些并行啊! - assylias
3
@assylias,我也是这样解读的。你的解决方案是真正的现实世界中的胜利案例,因为每个人都会同意,但这有助于满足所述要求。 - Marko Topolnik
1
@assylias,我现在明白你的意思了,我没有意识到这也是解释问题的一种方式:确保T2在T1开始运行后开始运行。这将是一个“Phaser”的工作。 - Marko Topolnik
1
那么我们似乎可以认为问题并不是很清楚。我从未参加过IT面试(不是程序员),但似乎他们经常提出奇怪、不精确且实用性很小的问题!无论如何... - assylias
1
@Assylias - 没错。这是一个关于一个愚蠢场景的烦人问题,很容易被误解。即使您通过在早期线程的run()方法中创建后续线程来强制执行运行顺序,调度程序仍可能因为某些不相关的硬件中断导致调度程序运行而随时更改线程状态。 - Martin James
显示剩余2条评论

6
@Assylias已经发布了显而易见且最简单的方法——让T1运行方法创建/启动T2和T2运行方法创建/启动T3。
在我看来,这几乎是毫无意义的,但确实可以这样做。
使用Join()的解决方案并不能回答问题,它们只能确保线程的终止有序进行,而不能确保它们的运行顺序。如果面试官没有意识到这一点,那么你需要找另一份工作。
在面试中,我的回答会是:“为什么要这样做?线程通常用于避免您正在询问的情况!”

5
有一种方法可以实现它,大概是这样的。虽然它很复杂,但你可能想使用java.util.concurrent.CyclicBarrier类来完成。
每个线程完成时都设置布尔值并通知下一个线程继续。即使它是一个AtomicBoolean类,我们仍需要synchronized以便在其上wait()notify()
将锁对象传入会更加清晰,或者可能有一个begin()方法在T2T3中,这样我们就可以将锁隐藏在这些对象内部。
final Object lock2 = new Object();
final Object lock3 = new Object();
boolean ready2;
boolean ready3;
...
public T1 implements Runnable {
    public void run() {
        ...
        synchronized (lock2) {
            // notify the T2 class that it should start
            ready2 = true;
            lock2.notify();
        }
    }
}
...

public T2 implements Runnable {
    public void run() {
        // the while loop takes care of errant signals
        synchronized (lock2) {
            while (!ready2) {
                lock2.wait();
            }
        }
        ...
        // notify the T3 class that it should start
        synchronized (lock3) {
            ready3 = true;
            lock3.notify();
        }
    }
}
...
public T3 implements Runnable {
    public void run() {
        // the while loop takes care of errant signals
        synchronized (lock3) {
            while (!ready3) {
                lock3.wait();
            }
        }
        ...
    }
}

1
我认为在 T2run() 方法中,你想要说的是 synchronized (lock2) { 而不是 synchronized (lock3) { - Kuldeep Jain
@Gray,在这种情况下使用AtomicBooleanBoolean相比是否有优势? - Learner
你不能在一个 Boolean 上进行同步,@John。这是一个常见问题。你可以有一个 final Object lock 和一个 boolean,但没有太大的意义。此外,AtomicBoolean 允许您在不必同步的情况下测试锁定。 - Gray
@Gray:我们能不能不使用(原子)整数,这样就不需要多个布尔锁变量了?只需检查<=1或2等等? - Danyal
你可以这样做 @Danyal,虽然我不确定这样做能节省多少时间。我的答案有一些竞态条件,所以我已经对其进行了修改。 - Gray

4
有线程T1,T2和T3,我们怎样才能确保T2在线程T1之后运行,T3在T2之后运行呢? 或者说有三个线程T1,T2和T3?你如何确保Java中的顺序为T1,T2,T3? 这个问题基本上是要求T3首先完成,其次是T2,最后是T1。 我们可以使用线程类的join()方法。为了确保三个线程执行,需要先启动最后一个线程,例如T3,并按相反的顺序调用join方法,比如T3调用T2.join,T2调用T1.join。这样,T1将首先完成,T3将最后完成。
public class Test1 {
    public static void main(String[] args) {
        final Thread t1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("start 1");
                System.out.println("end 1");
            }//run
        });

        final Thread t2  = new Thread(new Runnable() {
            public void run() {
                System.out.println(" start 2 ");
                try {
                    t1.join(2000);
                } catch (Exception e) {
                    e.getStackTrace();
                }
                System.out.println(" end 2");
            }
        }) ;

        final Thread t3 = new Thread( new Runnable() {
            public void run() {
                System.out.println(" start 3 ");

                try {
                    t2.join(4000);
                }catch(Exception e) {
                    e.getStackTrace();
                }
                System.out.println(" end  3 ");
            }
        });
       // we are reversing the order of the start() method
        t3.start();
        t2.start();
        t1.start();


    }
}

从输出结果可以看出,由于我们不知道哪个线程会获得CPU,所以线程的启动顺序是不确定的。这是线程调度器的决定,我们无法干涉。但是,你可以看到线程的结束顺序是正确的,即T1、T2和T3。

还有一种方法可以实现相同的效果,伪代码如下:

t1.start();

    t1.join(); // signals t2 to wait

    if( !t1.isAlive()) {
        t2.start();// if t1 is finished then t2 will start
    }

    t2.join();//signals t3 to wait

    if (!t2.isAlive()) {
        t3.start(); 
    }

让我们来看一个完整的程序:

 public class Tic implements Runnable{
   public void run() {
       try {
            for (int i = 0; i < 2; i++) {
                  System.out.println("tic");
            }
    } catch (Exception e) {
        // TODO: handle exception
        e.getStackTrace();
    }
   }
}

public class Tac implements Runnable{
       public void run() {
           try {
                for (int i = 0; i < 2; i++) {
                      System.out.println("tac");
                }
        } catch (Exception e) {
            // TODO: handle exception
            e.getStackTrace();
        }
       }
    }
public class Toe implements Runnable{
       public  void run() {
           try {
                for (int i = 0; i < 2; i++) {
                      System.out.println("toe");
                }
        } catch (Exception e) {
            // TODO: handle exception
            e.getStackTrace();
        }
       }
    }

public class RunThreads1 {
public static void main(String[] args) {
    try {

        Tic tic = new Tic();
        Tac tac = new Tac();
        Toe toe = new Toe();
        Thread t1 = new Thread(tic);
        Thread t2 = new Thread(tac);
        Thread t3 = new Thread(toe);
        t1.start();

        t1.join(); // signals t2 to wait

        if( !t1.isAlive()) {
            t2.start();// if t1 is finished then t2 will start
        }

        t2.join();//signals t3 to wait

        if (!t2.isAlive()) {
            t3.start(); 
        }

    }catch(InterruptedException e) {
        e.printStackTrace();
    }
}
}

输出结果为: tic tic tac tac toe toe

3

面试官问你的问题是三个线程按顺序完成工作。例如,如果一个线程打印1、4、5...第二个线程打印2、5、8和第三个线程打印3、6、9等,则输出应为1、2、3、4、5.....

第一个线程打印1并给第二个线程打印2的机会……以此类推。

我试着使用循环屏障来实现。一旦“one”打印了1,它会调用cb.wait并将机会交给two,当two运行时,它将以类似的方式调用three,并继续运行。请告诉我代码中是否存在任何错误。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

class one implements Runnable{
    CyclicBarrier cb;
    one(CyclicBarrier cb){this.cb=cb;}
    public void run(){
        int i=1;
        while(true)
        {
            System.out.println(i);
            try {
                Thread.sleep(1000);
                cb.await();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            i=i+3;
        }
    }

}
class two implements Runnable{
    CyclicBarrier cb;
    int i=2;
    two(CyclicBarrier cb){this.cb=cb;}
    public void run(){


        System.out.println(i);
        try {
            cb.await();
            i=i+3;
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}


public class oneTwoThree {
    public static void main(String args[]){
        Runnable threePrinter = new Runnable() {
            int i=3;
            public void run() {
                System.out.println(i);
                i=i+3;
            }
        };


        CyclicBarrier bar2 =new CyclicBarrier(1,threePrinter);//, barrier1Action);
        two twoPrinter  =new two(bar2);
        CyclicBarrier bar1 =new CyclicBarrier(1,twoPrinter);

        Thread onePrinter=new Thread(new one(bar1));
        onePrinter.start();
    }
}

今天面试时,我被问到了完全相同的问题。我无法回答。我一直在寻找这个问题的答案,最终在你的回复中找到了它。它完美地解决了我的问题。 - Siddhartha

3

线程也是可运行的。您可以简单地按顺序运行它们:

t1.run();
t2.run();
t3.run();

这显然没有什么兴趣。

假设他们希望线程并行运行,一种解决方案是让每个线程启动下一个线程,因为 JMM保证

对线程的start()调用发生在启动的线程中的任何操作之前。


9
那它们不是“线程”,只是对象而已。我怀疑这不会是一个好的面试答案。 - Gray
1
@Gray 我不能不同意你评论的最后一部分。 - assylias
1
@MarkoTopolnik 看来我的幽默感并没有得到一致的认可(毫不奇怪:我整天都在和英国人交流)- 我稍后会把它删除;-) - assylias
1
我明白了——是的,这个上下文有点微妙了 :) - Marko Topolnik
1
抱歉,伙计,虽然我想点赞两次,但我只能点赞一次。 :) - fmucar
显示剩余6条评论

3
在每个线程的开头(除了t1),让它调用它前面的线程的join()方法。使用执行器(而不是直接使用线程)也是另一种选择。还可以考虑使用信号量-semaphores-T1在完成后应释放许可证,T2应尝试获取两个许可证,并在完成后释放它们,T3应尝试获取三个许可证,以此类推。使用join或executors将是首选路线。

1
我们如何确保线程T2在T1之后运行,而线程T3在T2之后运行?
NOTE: Assuming that it is not about scheduling the threads in the required order 

我们可以使用 Condition 接口。

我们需要绑定两个条件到一个 Lock 上: condition1 用于协调 T1 和 T2,condition2 用于协调 T2 和 T3。将 condition1 传递给 T1 和 T2,将 condition2 传递给 T2 和 T3。

因此,在其运行方法中,T2 将 awaitcondition1 上,该条件将在 T1(从 T1 的运行方法中,在 T1 开始/完成任务后)signalled

类似地,在其运行方法中,T3 将 awaitcondition2 上,该条件将在 T2(从 T2 的运行方法中,在 T2 开始/完成任务后)signalled

1
我尝试了一种更简单的方法,使用等待和通知(与我之前发布的循环屏障方法相反)。
它使用一个“State”类...它有三个状态:1、2、3(默认为3)。当它处于3时,它会触发t1,在1时会触发t2,在2时会触发t3等等。
类: State// int i=3 T1// 打印1,4,7... T2// 打印2,5,8 T3//打印3,6,9等等,
请让我知道您的看法或代码中是否有任何问题。谢谢。
以下是代码:
public class State {

    private int state ;

    public State() {
        this.state =3;
    }

    public synchronized int getState() {
        return state;
    }

    public synchronized void setState(int state) {
        this.state = state;
    }


}


public class T1 implements Runnable {

    State s;
    public T1(State s) {
        this.s =s;
    }

    @Override
    public void run() {
        int i =1;

        while(i<50)
        {
            //System.out.println("s in t1 "+ s.getState());

            while(s.getState() != 3)
            {

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

                }

            }

            synchronized(s)
            {
                //if(s.getState() ==3)

                if(s.getState()==3)
                System.out.println("t1 "+i);
                s.setState(1);
                i = i +3 ;
                s.notifyAll();


            }

        }

    }

}




public class T2 implements Runnable {

    State s;
    public T2(State s) {
        this.s =s;
    }

    @Override
    public synchronized void run() {
        int i =2;

        while(i<50)
        {

            while(s.getState() != 1)
            {

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

                }

            }

            synchronized(s)
            {
                //if(s.getState() ==3)

                if(s.getState()==1)
                System.out.println("t2 "+i);
                s.setState(2);
                i = i +3 ;
                s.notifyAll();

            }

        }




    }


}



public class T3 implements Runnable {

    State s;
    public T3(State s) {
        this.s =s;
    }

    @Override
    public synchronized void run() {
        int i =3;

        while(i<50)
        {

            while(s.getState() != 2)
            {
                synchronized(s)
                {
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                }

            }

            synchronized(s)
            {

                if(s.getState()==2)
                System.out.println("t3 "+i);
                i = i +3 ;
                s.setState(3);
                s.notifyAll();
            }

        }



    }}





    public class T1t2t3 {
        public static void main(String[] args) {

            State s = new State();
            Thread t1 = new Thread(new T1(s));
            Thread t2 = new Thread(new T2(s));
            Thread t3 = new Thread(new T3(s));

            t1.start();
            t2.start();
            t3.start();

        }
    }

0
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

class Worker implements Runnable {

  BlockingQueue<Integer> q = new LinkedBlockingQueue<>();
  Worker next = null; // next worker in the chain

  public void setNext(Worker t) {
    this.next = t;
  }

  public void accept(int i) {
    q.add(i);
  }

  @Override
  public void run() {
    while (true) {
      int i;
      try {
        i = q.take(); // this blocks the queue to fill-up
        System.out.println(Thread.currentThread().getName() + i);
        if (next != null) {
          next.accept(i + 1); // Pass the next number to the next worker
        }
        Thread.sleep(500); // Just sleep to notice the printing.
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

public class PrintNumbersSequentially {
  public static void main(String[] as) {

    Worker w1 = new Worker();
    Worker w2 = new Worker();
    Worker w3 = new Worker();

    w1.setNext(w2);
    w2.setNext(w3);
    w3.setNext(w1);

    new Thread(w1, "Thread-1: ").start();
    new Thread(w2, "Thread-2: ").start();
    new Thread(w3, "Thread-3: ").start();

    //Till here all the threads have started, but no action takes place as the queue is not filled for any worker. So Just filling up one worker.
    w1.accept(100);
  }
}

我认为这可能会对你有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接