【Java】多线程系列(三)之阻塞线程的多种方法

前言:

在某些应用场景下,我们可能需要等待某个线程执行完毕,然后才能进行后续的操作。也就是说,主线程需要等待子线程都执行完毕才能执行后续的任务。
例如,当你在计算利用多线程执行几个比较耗时的任务的时候,主线程需要利用这几个线程计算的结果,才能进行后续的操作。那么我们其实就需要等待所有线程执行完毕。

这里,介绍几个常用的方法

线程执行单次的场景下

1,利用Thread类的join()方法

package concurrent;

import java.util.ArrayList;
import java.util.List;

public class JoinTest {
    public static void main(String[] args) {
        List<Thread> list=new ArrayList<>();
        for(int i=0;i<5;i++){
            Thread t=new Thread(){
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+" is running!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            list.add(t);
            t.start();
        }
        for(int i=0;i<5;i++){
            try {
                list.get(i).join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread()+" is running!");
    }

}

程序运行结果如下:

Thread-2 is running!
Thread-0 is running!
Thread-1 is running!
Thread-3 is running!
Thread-4 is running!
Thread[main,5,main] is running!

注:

对于上面的join()进行线程阻塞的时候,需要注意一下另外一种情况: 上面的5个线程,每次线程start()之后立即调用join()
这样会产生类似于单线程模式,即5个线程序列化形式组织,顺序执行。

2,利用ExecutorService的invokeAll()方法

package concurrent;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorServiceInvokeAllTest {
    public static void main(String[] args) {
        ExecutorService exec=Executors.newFixedThreadPool(5);
        List<Callable<String>> list=new ArrayList<>();
        for(int i=0;i<5;i++){
            callableTest cl=new callableTest();
            list.add(cl);
        }
        try {
            exec.invokeAll(list);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread()+" is running!");
        exec.shutdown();
    }
}
class callableTest implements Callable<String>{

    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread()+" is running!");
        Thread.sleep(1000);//模拟线程执行的耗时过程
        return null;
    }

}

程序运行结果:

Thread[pool-1-thread-1,5,main] is running!
Thread[pool-1-thread-3,5,main] is running!
Thread[pool-1-thread-2,5,main] is running!
Thread[pool-1-thread-5,5,main] is running!
Thread[pool-1-thread-4,5,main] is running!
Thread[main,5,main] is running!

invokeAll是一个阻塞方法,会等待任务列表中的所有任务都执行完成。等待所有的任务完成之后,就会返回一个Future的列表,里面记录了每个线程运行之后的结果。一旦ExecutorService.invokeAll()方法产生了异常,线程池中还没有完成的任务会被取消执行。

3,利用Future的get()方法

package concurrent;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FutureTest {
    public static void main(String[] args) {
        ExecutorService exc=Executors.newFixedThreadPool(5);
        Set<Future<Integer>> set = new HashSet<Future<Integer>>();
        for(int i=0;i<5;i++){
            callTest cl=new callTest();
            Future<Integer> f=exc.submit(cl);
            set.add(f);
        }
        int num=0;
        for(Future<Integer> f:set){
            try {
                num+=f.get();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread()+" is running!");
        System.out.println("The value num="+num);
        exc.shutdown();
    }
}
class callTest implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread()+" is running!");
        Thread.sleep(1000);
        return Integer.valueOf(1);
    }
}

输出结果如下:

Thread[pool-1-thread-1,5,main] is running!
Thread[pool-1-thread-3,5,main] is running!
Thread[pool-1-thread-2,5,main] is running!
Thread[pool-1-thread-4,5,main] is running!
Thread[pool-1-thread-5,5,main] is running!
Thread[main,5,main] is running!
The value num=5

Runnable是执行工作的独立任务,不返回任何结果。如果希望在任务完成之后,能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE5中引入的Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call()中返回的值,并且必须使用ExecutorService.submit()方法调用它。
submit()方法会产生Future对象,它用Callable返回的结果的特定类型进行了参数化。你可以使用isDone()方法来查看Future是否已经完成。当任务完成时,就会返回结果,可以调用get()方法来获取返回的结果。当然,也可以不用isDone()方法进行检查就可以直接调用get(),在这种情况下,get()方法就会被阻塞,直至返回结果就绪。

4,利用ExecutorService的awaitTermination()方法

package concurrent;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ESawaitTerminationTest {
    public static void main(String[] args) {
        ExecutorService exc=Executors.newFixedThreadPool(5);
        for(int i=0;i<5;i++){
            Thread thread=new Thread(){
                @Override
                public void run() {
                    System.out.println(Thread.currentThread()+" is running!");
                    try {
                        int temp=new Random().nextInt(5000);
                        System.out.println("sleep duration: "+temp+"ms");
                        sleep(temp);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            };
            exc.execute(thread);
        }
        exc.shutdown();
        try {
            while(!exc.awaitTermination(1, TimeUnit.SECONDS)){
                System.out.println("Executors is not terminated!");
            }
            System.out.println("Executors is terminated!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出结果如下:

Thread[pool-1-thread-2,5,main] is running!
Thread[pool-1-thread-1,5,main] is running!
Thread[pool-1-thread-3,5,main] is running!
sleep duration: 80ms
sleep duration: 468ms
sleep duration: 1200ms
Thread[pool-1-thread-5,5,main] is running!
sleep duration: 195ms
Thread[pool-1-thread-4,5,main] is running!
sleep duration: 2209ms
Executors is not terminated!
Executors is not terminated!
Executors is terminated!

上面的awaitTermination()方法也可以使用isTerminated()方法进行判断,同时在while()循环体内加一个Thread.sleep()方法,即每隔一段时间检查一遍线程池是否终止。这两个方法是同样的效果!

5,利用CountDownLatch

CountDownLatch类可以生成指定数目的锁,每把锁对应一个线程,每个线程执行完毕之后,就可以相应的减少一把锁。当锁的数目减为0之后,相当于所有的锁都已经”归还“,所有线程执行完毕。

package concurrent;

import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {
    public static void main(String[] args) {
        CountDownLatch cl=new CountDownLatch(5);
        for(int i=0;i<5;i++){
            Thread t=new Thread(){
                public void run() {
                    System.out.println(Thread.currentThread()+" is running!");
                    int temp=new Random().nextInt(5000);
                    System.out.println("sleep duration: "+temp+"ms");
                    try {
                        sleep(temp);
                        cl.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                };
            };
            t.start();
        }
        try {
            cl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread()+" is running!");
    }
}

输出结果如下:

Thread[Thread-0,5,main] is running!
Thread[Thread-3,5,main] is running!
Thread[Thread-2,5,main] is running!
Thread[Thread-1,5,main] is running!
sleep duration: 1105ms
sleep duration: 1169ms
sleep duration: 4168ms
sleep duration: 642ms
Thread[Thread-4,5,main] is running!
sleep duration: 1949ms
Thread[main,5,main] is running!

循环业务场景下的使用

很多时候,一些线程为了实现定时更新的功能,就会每隔一段时间,循环执行某个任务。

例如:

web开发中,主线程M,子线程A、B。M需要等待子线程A、B至少各自完成一次任务执行才能进行后面的任务执行。之后子线程A、B就每隔一段时间循环执行任务去更新数据。

那么这种场景下如何实现需求呢?

参见笔者之前的多线程系列中的一篇文章:

【Java】多线程系列(二)之CountDownLatch的使用

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 中有多种方式可以使线程阻塞,常见的有以下几种: 1. 调用 sleep 方法:调用 Thread 类的 sleep(long millis) 方法可以使当前线程阻塞指定的时间(毫秒),在此期间线程不会释放锁,但是不会占用 CPU 资源,适合在等待一段时间后再继续执行的场景。 2. 调用 wait 方法:调用 Object 类的 wait() 方法可以使当前线程等待,直到其它线程调用该对象的 notify() 或 notifyAll() 方法唤醒该线程,wait() 方法会释放对象锁,进入等待状态,等待其它线程通知。通常 wait() 方法需要和 synchronized 关键字一起使用,确保线程安全。 3. 调用 join 方法:调用 Thread 类的 join() 方法可以使当前线程等待另一个线程执行完毕后再继续执行,join() 方法阻塞当前线程,等待目标线程执行完毕,然后当前线程才会继续执行。 4. 调用 LockSupport.park 方法:调用 LockSupport 类的 park() 方法可以使当前线程阻塞,直到另一个线程调用该线程的 unpark() 方法唤醒该线程。park() 方法可以用于实现线程间的同步和控制,例如等待某个条件满足后再继续执行。 需要注意的是,线程阻塞线程挂起是不同的概念。线程阻塞是指线程在等待某个事件发生时被暂停执行,但是线程仍然处于存活状态,可以被唤醒继续执行。线程挂起是指线程被完全暂停,不再占用 CPU 资源,只有接收到特定的信号才能被唤醒。在 Java 中,线程阻塞方式通常是使用上述几种方法线程挂起通常是使用操作系统提供的机制,例如 POSIX 中的 sigsuspend() 函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值