Java多线程

本文介绍了Java并发编程中的关键工具类,包括CountDownLatch、CyclicBarrier、Semaphore、FutureTask、Executor、ScheduledExecutorService和CompletionService。详细阐述了这些工具类的作用、应用场景及示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.CountDownLatch

递减计数器锁,一个同步辅助类,可以用来确保某些活动直到其他活动都完成后才继续执行。它包含一个递减计数器,该计数器初始值是一个正数,表示需要等待事件的数量。

await()方法等待计数器到达0表示所需等待的事件都已完成。

例子:线程2等线程1执行完才能开始执行

CountDownLatch mLatch = new CountDownLatch(1);

new Thread() {
    public void run() {
        SystemClock.sleep(5 * 1000);
        System.out.println("线程1执行结束");
        mLatch.countDown();
    };
}.start();

new Thread() {
    public void run() {
        try {
            System.out.println("线程2等待线程1执行完毕");
            mLatch.await();
            System.out.println("线程2开始执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        SystemClock.sleep(15 * 1000);
        System.out.println("线程2执行结束");
    };
}.start();


2.CyclicBarrier

循环屏障,一个同步辅助类,它允许一组线程相互等待,直到达到某个公共屏障点之后再全部同时执行。当所有等待线程都被释放以后,CyclicBarrier可以被重用。

CyclicBarrier支持一个可选的Runnable命令,在一组线程中的最后一个线程达到之后,该命令只在每个屏障点运行一次。

例子:两个皮球装一箱,需要造10个皮球

int TOTAL_COUNT = 10;
int GROUP_COUNT = 2;
CyclicBarrier mBarrier = new CyclicBarrier(GROUP_COUNT, new Runnable() {

    @Override
    public void run() {
        System.out.println("够一箱了,装箱...");
    }

});

for (int i = 0; i < TOTAL_COUNT; i++) {
    new Thread("线程" + i) {
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName()
                        + "造皮球中...完毕后等待凑够一箱后装箱");
                mBarrier.await();
                System.out.println(Thread.currentThread().getName()
                        + "开始装箱");
            } catch (Exception e) {
                e.printStackTrace();
            }
        };
    }.start();
}


CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们的侧重点不同。

CountDownLatch一般用于某个线程A等待若干个其他线程都执行完之后,线程A才执行。

而CyclicBarrier一般用于一组线程相互等待至某个状态,然后这一组线程同时执行。

另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

3.semaphore

信号量,可以控制同时使用公共资源的线程个数。

semaphore管理着一个许可集,许可的初始数量可以通过构造函数来指定,执行操作时通过acquire()方法来获取许可,使用后通过release()方法释放许可。如果没有许可,则acquire()方法将会一直阻塞到有许可或被中断或者操作超时。

例子:10个人用一台打印机打印资料

Semaphore mSemaphore = new Semaphore(1);

for (int i = 0; i < 10; i++) {
    new Thread(){
        public void run() {
            print("打印资料...");
        };
    }.start();
}

private void print(String str) {
    try {
        mSemaphore.acquire();
        System.out.println("开始打印");
        SystemClock.sleep(10 * 1000);
        System.out.println("打印结束");
        mSemaphore.release();
    } catch (Exception e) {
        e.printStackTrace();
    }
}


4.FutureTask

未来任务,用来实现预加载资源。和Runnable类似,都可以通过Thread来启动。但是FutureTask可以返回执行完毕的结果,并且FutureTask的get()方法支持阻塞。我们可以用来预加载一些可能用到的资源,然后在需要用到的时候调用get()方法获取(如果资源加载完毕,则直接返回;否则会阻塞当前线程等待其加载完成)。

例子:加载新闻页或加载数据库信息

private volatile int mCurrentPage = 1;
private FutureTask<String> mFutureTask = new FutureTask<String>(
        new Callable<String>() {

            @Override
            public String call() throws Exception {
                return loadDateFromNet();
            }

        });

private String loadDateFromNet() {
    SystemClock.sleep(2 * 1000);
    return "第" + mCurrentPage + "页的数据是:***中500万大奖";
}

// 开始加载当前页数据
Thread thread = new Thread(mFutureTask);
thread.start();
for (int i = 0; i < 3; i++) {
    long start = System.currentTimeMillis();
    String currentPageContent = getCurrentPageContent();
    System.out.println("耗时:" + (System.currentTimeMillis() - start)
            + ", 内容:" + currentPageContent);
    SystemClock.sleep(5 * 1000);
}

private String getCurrentPageContent() {
    String result = null;
    try {
        // 获取当前页的数据
        result = mFutureTask.get();
        // 新开启futureTask加载下一页的数据
        mCurrentPage += 1;
        Thread thread = new Thread(mFutureTask = new FutureTask<String>(
                new Callable<String>() {

                    @Override
                    public String call() throws Exception {
                        return loadDateFromNet();
                    }

                }));
        thread.start();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return result;
}


5.Executor

执行者,只有一个execute(Runnable command)回调接口。执行已提交的Runnable任务对象。此接口提供一个将任务提交与每个任务将如何运行相分离的机制。通常使用Executor而不是显式地创建线程。

Java类库提供了很多静态方法来创建线程池:

newFixedThreadPool创建一个固定长度的线程池。

newCachedThreadPool创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时会增加线程数量,线程池规模无限制。

newSingleThreadPoolExecutor创建一个单线程的Executor。

newScheduledThreadPool创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer。

在线程池中执行任务比为每个任务分配一个线程优势更多,通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊线程创建和销毁产生的巨大开销。当请求达到时,通常工作线程已经存在,提供了响应性;通过配置线程池的大小,可以创建足够多的线程使CPU达到忙碌状态,还可以防止线程太多耗尽计算机资源。

例子:创建长度为CPU个数的线程池来处理10个线程

Executor mExecutor = Executors.newFixedThreadPool(Runtime.getRuntime()
        .availableProcessors());

for (int i = 0; i < 10; i++) {
    Runnable task = new Runnable() {

        @Override
        public void run() {
            System.out.println("线程" + Thread.currentThread().getName()
                    + "执行");
            SystemClock.sleep(3 * 1000);
        }

    };
    mExecutor.execute(task);
}


6.ScheduledExecutorService

安排执行服务

Timer存在一些缺陷:

a.Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务且任务时间过长,超过了两个任务的间隔时间,那么后面线程需要等到前面线程执行完之后才能开始执行。

b.如果TimerTask抛出RuntimeException,Timer会停止所有使用Timer执行的任务。

c.Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化,会出现一些执行上的变化,ScheduledExecutorService基于时间的延迟,不会由于系统时间的改变发生执行变化。

例子:两个延迟任务,一个延迟1秒,1个延迟5秒

ScheduledExecutorService mScheduledExecutorService = Executors
        .newScheduledThreadPool(2);
long mStartTime;


TimerTask task1 = new TimerTask() {

    @Override
    public void run() {
        System.out.println("开始执行时间:"
                + (System.currentTimeMillis() - mStartTime));
        SystemClock.sleep(10 * 1000);
    }

};

TimerTask task2 = new TimerTask() {

    @Override
    public void run() {
        System.out.println("开始执行时间:"
                + (System.currentTimeMillis() - mStartTime));
        SystemClock.sleep(2 * 1000);
    }

};
mStartTime = System.currentTimeMillis();
mScheduledExecutorService.schedule(task1, 1000, TimeUnit.MILLISECONDS);
mScheduledExecutorService.schedule(task2, 5 * 1000,
        TimeUnit.MILLISECONDS);


7.CompletionService

完成服务,它将Executor和BlockingQueue结合在一起,同时使用Callable作为任务的基本单元,整个过程就是生产者不断把Callable任务放入阻塞队列,Executor作为消费者不断把任务取出来执行并返回结果。哪个任务先执行完就直接返回,而不是按顺序返回,这样极大的提升了效率。

例子:声明一个线程池长度为3阻塞队列长度为2的CompletionService,并发执行10个任务

ExecutorService mExecutorService = Executors.newFixedThreadPool(3);
BlockingQueue<Future<Integer>> mBlockingQueue = new LinkedBlockingDeque<Future<Integer>>(
        2);
CompletionService<Integer> mCompletionService = new ExecutorCompletionService<Integer>(
        mExecutorService, mBlockingQueue);

for (int i = 0; i < 10; i++) {
    mCompletionService.submit(new Callable<Integer>() {
        
        @Override
        public Integer call() throws Exception {
            int num = new Random().nextInt(1000);
            SystemClock.sleep(num);
            System.out.println(Thread.currentThread().getName() + "休眠了" + num);
            return num;
        }
    });
}

for (int i = 0; i < 10; i++) {
    try {
        Future<Integer> take = mCompletionService.take();
        System.out.println("执行完返回的结果:" + take.get());
    } catch (Exception e) {
        e.printStackTrace();
    }
}


ExecutorService的invokeAll方法也能批量执行任务并返回结果,但是必须等待所有任务都执行完后才统一返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值