Java线程以及常用线程池相关内容总结

目录

Java线程总结

Java常用线程池总结

ExecutorService两种提交任务方式源码分析

线程池中涉及到的几个接口和类的关系图


Java线程总结

Java有三种实现线程的方式:

1. 继承Thread,然后重写run()方法

Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实体。启动线程的唯一方法就是通过Thread类的start()方法。start()方法是一个native方法,它将启动一个新线程,并执行run方法。

public class Work extends Thread {
    @Override
    public void run() {
        System.out.println("run start");
        System.out.println("Work extends Thread runing...");
        System.out.println("run end");
    }
}
public class Test {
    public static void main(String[] args) {
        Work work = new Work();
        work.start();
    }
}

执行结果:

2. 直接实现Runnable接口,实现run()方法

public class Work implements Runnable{
    @Override
    public void run() {
        System.out.println("run start");
        System.out.println("Work implements Runnable...");
        System.out.println("run end");
    }
}

为了执行Work,需要首先实例化一个Thread,并传入自己的Work

public class Test {
    public static void main(String[] args) {
        Work work = new Work();
        Thread thread = new Thread(work);
        thread.start();
    }
}

执行结果:

当传入一个Runnable target参数传给Thread后,Thread的run()方法就会调用target.run(),jdk源码:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

3. 实现Callable接口

和Runnable最大的不同就是Callable的call方法有返回值,返回值封装在FutureTask对象中。

还有一点不同就是,实现了Callable的任务,执行任务需要先实例化一个FutureTask,将实现了Callable的任务通过参数传入FutureTask,然后调FutureTask的run方法执行任务。

public class Work implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("call start");
        System.out.println("Work implements Callable<String> running...");
        System.out.println("call end");
        return "success";
    }
} 

FutureTask继承了Runnable和Future两个接口

有两种运行方式,第一种方式才是正确的使用方法,第一种方式是通过另外一个线程去执行任务的,是真正的异步;第二种方式直接执行了call方法。

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Work work = new Work();
        FutureTask<String> task = new FutureTask<>(work);
        //第一种运行方式
        Thread thread = new Thread(task);
        thread.start();
        System.out.println("执行结果:" + task.get());

        //第二种运行方式
        task.run();
        System.out.println("执行结果:" + task.get());

    }
}

另外,FutureTask有两个构造方法,一个就是上面的,接收Callable任务,另外一个构造方法可以接收Runnable任务,但是如果接收Runnable任务的时候,也必须传入一个返回值,最后通过task获取到的返回值就是传入的值,这其实相当于返回值就没起什么作用,这样就和前面通过Runnable实现线程没什么区别了,而且还没有前面的简洁,一般不推荐这种用法。

如下:

public class Work1 implements Runnable {
    @Override
    public void run() {
        System.out.println("run start");
        System.out.println("Work1 implements Runnable running...");
        System.out.println("run end");
    }
}
Work1 work1 = new Work1();
FutureTask<String> task = new FutureTask<>(work1, "000");
Thread thread1 = new Thread(task);
thread1.start();

 

Java常用线程池总结

newCachedThreadPool:创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程。是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过60秒,则被终止并移出缓存;长时间闲置时,这种线程池不会消耗什么资源。其内部使用SynchronousQueue作为工作队列。创建线程池的时候可以通过参数指定ThreadFactory。

newFixedThreadPool:创建一个定长的线程池,可控制线程最大并发数,超出的线程会在队列中等待。内部使用LinkedBlockingQueue作为工作队列。创建线程池的时候可以通过参数指定核心线程数和ThreadFactory。

newScheduleThreadPoolExecutor:创建一个定长的线程池,支持定时及周期性任务执行。内部使用DelayedWorkQueue作为工作队列。创建的时候可以通过参数指定核心线程数和ThreadFactory。另外还有newSingleThreadScheduleExecutor(只有一个工作线程);newWorkStealingPool(java8才加入的一个方法,内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序)。

newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有的任务按照指定的顺序(FIFO,LIFO,优先级)执行。内部也是使用LinkedBlockingQueue作为工作队列。创建的时候可以指定ThreadFactory。

newCachedThreadPool

/**
创建一个可缓冲线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不是每次新建线程
*/
private static void newCacheThreadPool() {
    ExecutorService service = Executors.newCachedThreadPool();
    for(int i = 0; i < 10; i ++) {
        final int index = i;
        try {
            Thread.sleep(index * 1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        service.execute(() -> {
            System.out.println(index);
        });
    }
    service.shutdown();
}

newFixedThreadPool

/**
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印三个数字
定长线程池的大小最好根据系统资源进行设置,如Runtime.getRuntime().availableProcessors()
*/
private static void newFixedThreadPool() {
    
    ExecutorService service = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 10; i ++) {
        final int index = i;
        service.execute(() -> {
            System.out.println(new Date() + ":" + index);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
    service.shutdown();
} 

执行结果:

newScheduleThreadPool

创建一个定长线程池,支持定时及周期性任务执行。

一、

//延迟3秒执行
private static void test1(ScheduledExecutorService service) {
        System.out.println(new Date());
        service.schedule(() -> {
            System.out.println(new Date() + ":delay 3 seconds");
        }, 3, TimeUnit.SECONDS);
}

执行结果:

Thu Mar 07 11:15:35 CST 2019
Thu Mar 07 11:15:38 CST 2019:delay 3 seconds

二、

/*
延迟1秒后每3秒执行一次

该方法第三个参数标识在上一个任务开始执行之后延迟多少秒后再执行,是从上一个任务开始时开始计算
但是还是会等上一个任务执行完之后,下一个任务才开始执行,最后的结果是,就是感觉延迟失去了作用
*/
private static void test2(ScheduledExecutorService service) {
        System.out.println(new Date());
        service.scheduleAtFixedRate(() -> {
            System.out.println(new Date());
            try {
                Thread.sleep(5000);
                System.out.println(new Date() + ":delay 1 seconds, and execute every 3 seconds");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, 3, TimeUnit.SECONDS);
}

执行结果:

三、

//该方法第三个参数表示在上一个任务执行结束之后延迟多少秒之后再执行,是从上一个任务结束时开始计算的
private static void test3(ScheduledExecutorService service) {
        System.out.println(new Date());
        service.scheduleWithFixedDelay(() -> {
            System.out.println(new Date());
            try {
                Thread.sleep(5000);
                System.out.println(new Date() + ":delay 1 seconds, and execute every 3 seconds");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, 3, TimeUnit.SECONDS);

}

执行结果:

newSingleThreadExecutor

//创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行
private static void testNewSingleTheadPool() {
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i ++) {
            final int index = i;
            service.execute(() -> {
                try {
                    System.out.println(index);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
}

ExecutorService两种提交任务方式源码分析

首先是两种提交方式

exetuce(Runnable command):没有返回值

submit:有返回值,该方法有三个重载方法

  • public Future<?> submit(Runnable task)
  • public <T> Future<T> submit(Runnable task, T result)
  • public <T> Future<T> submit(Callable<T> task)

首先看看execute

平常最常用的就是通过这个方法提交任务了。一步一步往下看

execute方法源码如下:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    //从这儿也可以看出,当线程数没有达到核心线程数的话,只要提交任务,就会新创建一个线程
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //如果核心线程满了,并且没有空闲线程,则将任务放入队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //如果对垒也满了,则尝试新增线程,如果线程也达到最大线程,则执行任务拒绝策略
    else if (!addWorker(command, false))
        reject(command);
}

添加任务的方法就是addWorker方法了,源码就不贴出来了,这个方法中用到了锁,AtomicInteger和ReentrantLock,有兴趣的同学可以翻源码看看。

public Future<?> submit(Runnable task)

看一下源码

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    //将任务封装到RunnableFuture中(继承了Runnable和Future)
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
//返回的对象是一个FutureTask
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}
//将Runnable封装成一个Callable对象
public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}
//这儿的RunnableAdapter在代码里是一个内部类(Executors的一个内部类)
public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
        throw new NullPointerException();
    return new RunnableAdapter<T>(task, result);
}
//从这个内部类可以看到,最终call方法执行的时候仍然是调的Runnable的run方法,并且返回的result是一个null
static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
}

这样一看,就清晰多了,其实入参只有一个Runnable类型的submit方法相当于是没有返回值的(因为返回值是null),本质上和execute是没有区别的,只是在Runnable的外面套了一层,然后看起来是Callable。

最终执行任务是调方法execute(ftask)执行的,这就是调没有返回值的exetuce(Runnable command)方法。

public <T> Future<T> submit(Runnable task, T result)

这个方法跟上一个方法比,只是多了一个返回值,即如果任务执行完返回一个特定的结果(这个结果就是我们传入的result),执行流程跟第上一个方法是一样的,上一个方法返回的结果是null,这个方法放回的结果是我们传入的值。

这有点像我在第一部分“java线程总结”里面第三点中提到的,FutureTask也可以接收Runnable任务(其实是一样的),代码贴过来:

Work1 work1 = new Work1();
FutureTask<String> task = new FutureTask<>(work1, "000");
Thread thread1 = new Thread(task);
thread1.start();

public <T> Future<T> submit(Callable<T> task)

同样刷一遍源码

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}
public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

这个过程比submit传入Runnable的过程就就要简洁多了,因为传入的就是Callable对象,而传入的是Runnable对象,则需要将Runnable封装成Callable对象。

所以平常如果不需要返回值的话,直接用execute方法就可以了,如果需要返回值的话可以用入参是Callable的submit。

另外,当调Future的无参get的时候,会一直阻塞当前线程,直到得到返回值(任务线程执行完成),可以的话,尽量考虑使用带超时参数的get方法。get方法阻塞当前线程的时候用到了LockSupport中的park方法,关于这个方法的说明可以参考这篇博文:https://www.jianshu.com/p/e3afe8ab8364

关于Future的使用注意点推荐一篇博文:https://blog.csdn.net/xiongxianze/article/details/79282579

线程池中涉及到的几个接口和类的关系图

ThreadPoolExecutor的使用可以参考另一篇博文:https://blog.csdn.net/u012131610/article/details/87289985

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值