Java多线程知识点整理(CyclicBarrier、CountDownLatch、Callable、Future和FutureTask)

1、CyclicBarrierjava

    CyclicBarrier从字面理解是指循环屏障,它能够协同多个线程,让多个线程在这个屏障前等待,直到全部线程都达到了这个屏障时,再一块儿继续执行后面的动做。安全

代码:多线程

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierThread extends Thread{
	
	private CyclicBarrier cb;
	
	private int second;
	
	
	public CyclicBarrierThread(CyclicBarrier cb, int second) {
		
		this.cb = cb;
		this.second = second;
	}


	@Override
	public void run() {
		try {
			System.out.println("开始了!");
			Thread.sleep(second*1000);
			System.out.println("准备等待!"+System.currentTimeMillis());
			cb.await();
			System.out.println("结束了");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				System.out.println("CyclicBarrier的全部线程await()结束了,我运行了, 时间为"+System.currentTimeMillis());
				
			}
		};
		 	CyclicBarrier cb = new CyclicBarrier(3, runnable);
		    CyclicBarrierThread cbt0 = new CyclicBarrierThread(cb, 3);
		    CyclicBarrierThread cbt1 = new CyclicBarrierThread(cb, 6);
		    CyclicBarrierThread cbt2 = new CyclicBarrierThread(cb, 9);
		    cbt0.start();
		    cbt1.start();
		    cbt2.start();
		
	}
	
}

运行的结果:异步

从代码的结果来看,先运行run方法,遇到await()以后,睡眠,而后在运行runnable的方法,而后再运行run方法进行结束。ide

从使用来看,可能有人以为CyclicBarrier和CountDownLatch有点像,都是多个线程等待相互完成以后,再执行后面的代码。实际上,CountDownLatch和CyclicBarrier都是用于多个线程间的协调的,它们两者的几个差异是:this

一、CountDownLatch是在多个线程都进行了latch.countDown()后才会触发事件,唤醒await()在latch上的线程,而执行countDown()的线程,执行完countDown()后会继续本身线程的工做;CyclicBarrier是一个栅栏,用于同步全部调用await()方法的线程,而且等全部线程都到了await()方法时,这些线程才一块儿返回继续各自的工做。spa

二、另外CountDownLatch和CyclicBarrier的一个差异是,CountDownLatch不能循环使用,计数器减为0就减为0了,不能被重置,CyclicBarrier能够循环使用。线程

三、CountDownLatch能够唤起多条线程的任务,CyclicBarrier只能唤起一条线程的任务。设计

注意,由于使用CyclicBarrier的线程都会阻塞在await方法上,因此在线程池中使用CyclicBarrier时要特别当心,若是线程池的线程过少,那么就会发生死锁了。code

2、CountDownLatch

    CountDownLatch主要提供的机制是当多个(具体数量等于初始化CountDownLatch时count参数的值)线程都达到了预期状态或完成预期工做时触发事件,其余线程能够等待这个事件来触发本身的后续工做。值得注意的是,CountDownLatch是能够唤醒多个等待的线程的。到达本身预期状态的线程会调用CountDownLatch的countDown方法,等待的线程会调用CountDownLatch的await方法。若是CountDownLatch初始化的count值为1,那么这就退化为一个单一事件了,便是由一个线程来通知其余线程,效果等同于对象的wait和notifyAll,count值大于1是经常使用的方式,目的是为了让多个线程到达各自的预期状态,变为一个事件进行通知,线程则继续本身的行为。

import java.util.concurrent.CountDownLatch;

public class WorkThread extends Thread{
	
	private CountDownLatch cdl;
    private int sleepSecond;
        
    public WorkThread(String name, CountDownLatch cdl, int sleepSecond)
    {
        super(name);
        this.cdl = cdl;
        this.sleepSecond = sleepSecond;
    }
     
    @Override
    public void run()
    {
        try
        {
            System.out.println(this.getName() + "启动了,时间为" + System.currentTimeMillis());
            Thread.sleep(sleepSecond * 1000);
            cdl.countDown();
            System.out.println(this.getName() + "执行完了,时间为" + System.currentTimeMillis());
        } 
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
    private static class DoneThread extends Thread
    {
        private CountDownLatch cdl;
            
        public DoneThread(String name, CountDownLatch cdl)
        {
            super(name);
            this.cdl = cdl;
        }
            
        public void run()
        {
            try
            {
                System.out.println(this.getName() + "要等待了, 时间为" + System.currentTimeMillis());
                cdl.await();
                System.out.println(this.getName() + "等待完了, 时间为" + System.currentTimeMillis());
            } 
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
        
    public static void main(String[] args) throws Exception
    {
        CountDownLatch cdl = new CountDownLatch(3);
        DoneThread dt0 = new DoneThread("DoneThread1", cdl);
        //DoneThread dt1 = new DoneThread("DoneThread2", cdl);
        dt0.start();
        //dt1.start();
        WorkThread wt0 = new WorkThread("WorkThread1", cdl, 2);
        WorkThread wt1 = new WorkThread("WorkThread2", cdl, 3);
        WorkThread wt2 = new WorkThread("WorkThread3", cdl, 4);
        wt0.start();
        wt1.start();
        wt2.start();
    }
	
	
	
	
}

运行结果:

这至关因而一种进化版本的等待/通知机制,它能够的实现的是多个工做线程完成任务后通知多个等待线程开始工做,以前的都是一个工做线程完成任务通知一个等待线程或者一个工做线程完成任务通知全部等待线程。

CountDownLatch实际上是颇有用的,特别适合这种将一个问题分割成N个部分的场景,全部子部分完成后,通知别的一个/几个线程开始工做。好比我要统计C、D、E、F盘的文件,能够开4个线程,分别统计C、D、E、F盘的文件,统计完成把文件信息汇总到另外一个/几个线程中进行处理。

3、Callable、Future和FutureTask

3.一、Callable

Callable和rRunnable差很少,二者都是为那些其实例可能被另外一个线程执行的类而设计的,最主要的差异在于Runnable不会返回线程运算结果,Callable能够(假如线程须要返回运行结果)。

3.二、Future

Future是一个接口表示异步计算的结果,它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。Future提供了get()、cancel()、isCancel()、isDone()四种方法,表示Future有三种功能:

一、判断任务是否完成

二、中断任务

三、获取任务执行结果

3.三、FutureTask

FutureTask是Future的实现类,它提供了对Future的基本实现。可以使用FutureTask包装Callable或Runnable对象,由于FutureTask实现了Runnable,因此也能够将FutureTask提交给Executor。

使用方法

Callable、Future、FutureTask通常都是和线程池配合使用的,由于线程池ThreadPoolExecutor的父类AbstractExecutorService提供了三种submit方法:

一、public Future<?> subit(Runnable task){...}

二、public <T> Future<T> submit<Runnable task, T result>{...}

三、public <T> Future<T> submit<Callable<T> task>{...}

第2个用得很少,第1个和第3个比较有用。

代码:Callable和Future

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 CallableThread implements Callable<String>{

	@Override
	public String call() throws Exception {
		System.out.println("开始了call方法"+System.currentTimeMillis());
		Thread.sleep(1000);
		return "sucess";
	}
	
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		 ExecutorService es = Executors.newFixedThreadPool(1);
		 
		 CallableThread callableThread = new CallableThread();
		 
		 Future<String> submit = es.submit(callableThread);
		 es.shutdown();
		 try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		 System.out.println("mian线程等待!");
		 String string = submit.get();
		 System.out.println(string);
	}
}

运行结果:

Callable+FutureTask使用示例:

public static class CallableThread implements Callable<String>
{
    public String call() throws Exception
    {
        System.out.println("进入CallableThread的call()方法, 开始睡觉, 睡觉时间为" + System.currentTimeMillis());
        Thread.sleep(10000);
        return "123";
    }
}
    
public static void main(String[] args) throws Exception
{
    ExecutorService es = Executors.newCachedThreadPool();
    CallableThread ct = new CallableThread();
    FutureTask<String> f = new FutureTask<String>(ct);
    es.submit(f);
    es.shutdown();
        
    Thread.sleep(5000);
    System.out.println("主线程等待5秒, 当前时间为" + System.currentTimeMillis());
        
    String str = f.get();
    System.out.println("Future已拿到数据, str = " + str + ", 当前时间为" + System.currentTimeMillis());
}

总结:使用Callable、Future和FutureTask的好处

上面演示了两个例子,其实反映的是现实中一种状况,把上面的例子稍微扩展一下就是:

有一个method()方法,方法中执行方法A返回一个数据要10秒钟,A方法后面的代码一共要执行20秒钟,可是这20秒的代码中有10秒的方法并不依赖方法A的执行结果,有10秒钟的代码依赖方法A的执行结果。此时若采用同步的方式,那么势必要先等待10秒钟,等待方法A执行完毕,返回数据,再执行后面20秒的代码。

不得不说这是一种低效率的作法。有了Callable、Future和FutureTask,那么:

一、先把A方法的内容放到Callable实现类的call()方法中

二、method()方法中,Callable实现类传入Executor的submit方法中

三、执行后面方法中10秒不依赖方法A运行结果的代码

四、获取方法A的运行结果,执行后面方法中10秒依赖方法A运行结果的代码

这样代码执行效率一会儿就提升了,程序没必要卡在A方法处。异步执行代码。

固然,也能够不用Callable,采用实现Runnable的方式,run()方法执行完了想个办法给method()方法中的某个变量V赋个值就行了。可是我上一篇文章开头就说了,之因此要用多线程组件,就是由于JDK帮咱们很好地实现好了代码细节,让开发者更多能够关注业务层的逻辑。若是使用Runnable的方式,那么咱们本身就要考虑不少细节,好比Runnable实现类的run()方法执行完毕给V赋值是否线程安全、10秒后若是A方法没有执行完致使V尚未值怎么办,况且JDK还给用户提供了取消任务、判断任务是否存在等方法。既然JDK已经帮咱们考虑并实现这些细节了,在没有有说服力的理由的状况下,咱们为何还要本身写run()方法的实现呢? 

相关文章
相关标签/搜索