问题:
现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完之后执行,T3在T2执行完之后执行?
方法1:使用join
使用Thread原生方法join,join方法是使所属的线程对象x正常执行run()方法中的任务,而当前线程进行无限的阻塞,等到线程x执行完成后再继续执行当前线程后面的代码。
public static void main(String[] args) {
final Thread T1 = new Thread(new Runnable() {
public void run() {
System.out.println("T1 run");
}
});
final Thread T2 = new Thread(new Runnable() {
public void run() {
System.out.println("T2 run");
try{
T1.join();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("T2 run end");
}
});
Thread T3 = new Thread(new Runnable() {
public void run() {
System.out.println("T3 run");
try{
T2.join();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("T3 run end");
}
});
T1.start();
T2.start();
T3.start();
}
方法2:使用线程间通信的等待/通知机制
wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。
在调用wait之前,线程必须获取到该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。
private static boolean T2Run = false; //标识位,用来通知T2线程执行
private static boolean T3Run = false;
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
Thread T1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock1){
System.out.println("T1 run");
//t1 线程通知t2执行
T2Run = true;
lock1.notify();
}
}
});
Thread T2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock1){
if(!T2Run){
System.out.println("T2 wait");
try {
lock1.wait();
} catch (Exception e){
e.printStackTrace();
}
}
System.out.println("T2 run");
//t2 线程通知t3执行
synchronized (lock2){
T3Run = true;
lock2.notify();
}
}
}
});
Thread T3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock2){
if (!T3Run){
System.out.println("T3 wait");
try {
lock2.wait();
} catch (Exception e){
e.printStackTrace();
}
}
System.out.println("T3 run");
}
}
});
T1.start();
T2.start();
T3.start();
}
方法3:使用Conditon
关键字synchronized与wait和notify/notifyAll方法相结合可以实现等待/通知模式,类ReetrantLock也可以实现同样的功能,但需要借助于Condition对象。Condition可以实现多路通知,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。在使用notify/notifyAll通知时,被通知的线程却是由JVM随机选择的。
/**
* 使用condition
*/
private Lock lock = new ReentrantLock();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private static Boolean t2Run = false;
private static Boolean t3Run = false;
private void useCondition(){
Thread T1 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock(); //获取锁
System.out.println("T1 run");
t2Run = true; //设置t2可以运行
System.out.println("T1 run finish signal T2");
condition2.signal(); //通知T2执行
lock.unlock(); //解锁当前线程
System.out.println("T1 unlock");
}
});
Thread T2 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try{
if (!t2Run){
condition2.await(); //如果是false ,则等待
}
//若是true,则代表T2可以执行
System.out.println("T2 run");
t3Run = true;
condition3.signal();
System.out.println("T2 run finish signal T3");
}catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
Thread T3 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try{
if (!t3Run){
condition3.await(); //如果是false ,则等待
}
//若是true,则代表T2可以执行
System.out.println("T3 run");
}catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
T1.start();
T2.start();
T3.start();
}
方法4:使用线程池
使用newSingleThreadExecutor线程池,由于核心线程数只有一个,所以能够顺序执行。
/**
* 线程池
* 核心线程数:1
* 最大线程数:1
* 在日常中不建议使用newSingleThreadExecutor,因为阻塞队列个数没有限制,会导致内存溢出
*
*/
static ExecutorService executorService = Executors.newSingleThreadExecutor();
public static void main(String[] args) {
Thread T1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("T1 run");
}
});
Thread T2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("T2 run");
}
});
Thread T3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("T3 run");
}
});
executorService.submit(T1);
executorService.submit(T2);
executorService.submit(T3);
executorService.shutdown();
}
方法5:使用线程的CountDownLatch
CountDownLatch 的作用是:当一个线程需要另外一个或多个线程完成后,再开始执行。比如主线程要等待一个子线程完成环境相关配置的加载工作,主线程才继续执行,就可以利用 CountDownLatch 来实现。
比较重要的方法:
CountDownLatch(int count); //构造方法,创建一个值为count 的计数器
await();//阻塞当前线程,将当前线程加入阻塞队列。
countDown();//对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。
/** * 计数器1 用于T1线程通知T2线程 * 注意:这里个数都设置成立1 ,当T1执行完成后,执行countDown,来通知T2线程 */ static CountDownLatch countDownLatch1 = new CountDownLatch(1); /** * 计数器2 用于T2线程通知T3线程 */ static CountDownLatch countDownLatch2 = new CountDownLatch(1); public static void main(String[] args) { Thread T1 = new Thread(new Runnable() { @Override public void run() { System.out.println("T1 run"); countDownLatch1.countDown(); System.out.println("T1 countDown finish"); } }); Thread T2 = new Thread(new Runnable() { @Override public void run() { try{ countDownLatch1.await(); }catch (Exception e){ e.printStackTrace(); } System.out.println("T2 run"); countDownLatch2.countDown(); } }); Thread T3 = new Thread(new Runnable() { @Override public void run() { try{ countDownLatch2.await(); }catch (Exception e){ e.printStackTrace(); } System.out.println("T3 run"); } }); }
方法6:使用cyclicbarrier (多个线程互相等待,直到到达同一个同步点,再继续一起执行。)
比较重要的方法
CyclicBarrier(int parties) //构造方法,参数表示拦截的线程的个数
CyclicBarrier(int parties, Runnable barrierAction) //也是构造方法,可以通过后面的参数,这是线程的优先级
await() //告诉CyclicBarrier自己已经到达同步点,然后当前线程被阻塞,当所有线程都到达同步点(barrier)时,唤醒所有的等待线程,一起往下继续运行,可根据参数barrierAction决定优先执行的线程
/** * 设置2个线程互相等待,直到到达同一个同步点,再继续一起执行。T1不执行完,T2就永远不会执行 */ static CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2); /** * 设置2个线程互相等待,直到到达同一个同步点,再继续一起执行。 */ static CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2); public static void main(String[] args) { Thread T1 = new Thread(new Runnable() { @Override public void run() { System.out.println("T1 run"); try{ cyclicBarrier1.await(); }catch (Exception e){ e.printStackTrace(); } } }); Thread T2 = new Thread(new Runnable() { @Override public void run() { try{ cyclicBarrier1.await(); }catch (Exception e){ e.printStackTrace(); } System.out.println("T2 run"); try{ cyclicBarrier2.await(); }catch (Exception e){ e.printStackTrace(); } } }); Thread T3 = new Thread(new Runnable() { @Override public void run() { try{ cyclicBarrier2.await(); }catch (Exception e){ e.printStackTrace(); } System.out.println("T3 run"); } }); T1.start();; T2.start(); T3.start(); }
方法7:使用信号量 Semaphore
Semaphore计数信号量,常用于限制可以访问某些资源(物理或逻辑的)线程数目。
常用的方法:
Semaphore(int permits);//构造方法,permits就是允许同时运行的线程数目
public Semaphore(int permits,boolean fair);//permits就是允许同时运行的线程数目 ,fair 是否为公平锁,如果是公平锁,那么获得锁的顺序与线程启动顺序有关
void acquire()// 从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
tryAcquire
() //
尝试获得令牌,返回获取令牌成功或失败,不阻塞线程
。
release() //
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
/** * 设置信号量初始值为0 ,让T1 把信号量+1,这样,T2就可以执行了 */ static Semaphore semaphore1 = new Semaphore(0); static Semaphore semaphore2 = new Semaphore(0); public static void main(String[] args) { Thread T1 = new Thread(new Runnable() { @Override public void run() { System.out.println("T1 run"); try{ semaphore1.release(); }catch (Exception e){ e.printStackTrace(); } System.out.println("T1 semaphore1 + 1"); } }); Thread T2 = new Thread(new Runnable() { @Override public void run() { try{ semaphore1.acquire(); }catch (Exception e){ e.printStackTrace(); } System.out.println("T2 semaphore2 + 1"); try{ semaphore2.release(); }catch (Exception e){ e.printStackTrace(); } System.out.println("T2 run"); } }); Thread T3 = new Thread(new Runnable() { @Override public void run() { try{ semaphore2.acquire(); }catch (Exception e){ e.printStackTrace(); } System.out.println("T3 run"); } }); T1.start();; T2.start(); T3.start(); }
总结:共有7中方法
-
使用Thread原生方法join
-
使用线程间通信的等待/通知机制
-
使用Conditon
-
使用线程池
-
使用线程的CountDownLatch
- 使用cyclicbarrier (多个线程互相等待,直到到达同一个同步点,再继续一起执行)
-
使用信号量 Semaphore
参考:
https://www.cnblogs.com/wenjunwei/p/10573289.html