Java多线程和高并发学习笔记

【摘要】 Callable接口我们知道,java中实现多线程的方式有好几种,下面我们就来看看Callable接口代码很简单:// 实现Callable接口class Demo implements Callable<Integer> {    @Override    public Integer call() {        System.out.println(“come in Callable…

Callable接口

我们知道,java中实现多线程的方式有好几种,下面我们就来看看Callable接口

代码很简单:

// 实现Callable接口

class Demo implements Callable<Integer> {

@Override

public Integer call() {

System.out.println(“come in Callable”);

try {

TimeUnit.SECONDS.sleep(3);

} catch (InterruptedException e) {

e.printStackTrace();

}

return 1024;

}

}

用法也很简单

// 声明一个FutureTask接口才可以执行实现Callable接口的类

// FutureTask可以复用,如果有多个线程调用了futureTask,只会执行一次,

// 如果要执行多次,那么就需要声明多个FutureTask

FutureTask<Integer> futureTask = new FutureTask<>(new Demo());

Thread thread = new Thread(futureTask, “a”);

thread.start();

我们要强调的重点是它和Runnable接口的区别

// 如果a线程没有运行完,那么就等着

while (!futureTask.isDone()) {

// Callable可以获取到返回值,但是Runnable没有办法获取到返回值

// 两个线程:一个main线程,一个a线程,

try {

int result01 = 123;

// 如无必要,建议放到最后,要求获得a线程的运行结果,

// 如果没有获取到,就会阻塞线程,直到获取到结果,所以放到最后,可以防止线程阻塞

System.out.println(futureTask.get() + result01);

} catch (InterruptedException | ExecutionException e) {

e.printStackTrace();

}

}

结果:

首先,我们要知道,我们的电脑是一个cpu对应多个核心数,具体怎么理解呢?可以参考下面的说明

核,你可以想象成人脑子,一个核就是一个脑子,四核就说明CPU有四个脑子,脑子越多解决问题的速度越快,Intel的核技术,可以把一个核模拟分成两个线程来用,充分的发挥了cpu的性能,线程8就代表核心数是4核的处理器可以模拟出8个线程来使用。线程我们可以理解每个大脑同时能处理多少件事。

// 查看当前电脑是几核的

System.out.println(Runtime.getRuntime().availableProcessors());

而我们日常生产环境中,基本不会单一的创建单个线程,而是通过线程池的方式帮助我们去创建和管理线程,那么线程池的作用和好处有哪些呢?

线程池的作用:控制运行的线程数量

线程池的特点:线程复用,控制最大并发数量,管理线程

线程池的处理过程:先将任务放入队列,在线程创建后启动这些任务,如果线程数量超过了最大数量,那么超出的数量线程排队等候,

等待其他线程执行完成后,再从队列中取出任务来执行

线程池的好处:

1、降低资源消耗。通过重复利用已经创建的线程降低线程创建和消耗造成的消耗

2、提高响应速度。当任务到达时,不需要等待线程创建就能立即执行

3、提高线程的可管理性,线程是稀缺资源,如果无限的创建,不仅会消耗系统资源,且还会降低系统的稳定性,使用线程池可以进行同一的分配,调优和监控

4、底层都是ThreadPoolExecutor

常见的线程池的种类:

// 线程池固定线程数量

ExecutorService service = Executors.newFixedThreadPool(3);

// 线程池内只有1个线程

ExecutorService service2 = Executors.newSingleThreadExecutor();

// 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

ExecutorService service3 = Executors.newCachedThreadPool();

try {

// execute和submit都属于线程池的方法,execute只能提交Runnable类型的任务,而submit既能提交Runnable类型任务也能提交Callable类型任务。

// execute会直接抛出任务执行时的异常,submit会吃掉异常,可通过Future的get方法将任务执行时的异常重新抛出。

// execute所属顶层接口是Executor,submit所属顶层接口是ExecutorService

// 实现类ThreadPoolExecutor重写了execute方法,抽象类AbstractExecutorService重写了submit方法。

for (int i = 0; i < 6; i++) {

service.execute(() -> System.out.println(Thread.currentThread().getName()));

}

} catch (Exception e) {

e.printStackTrace();

} finally {

service.shutdown();

}

提交任务后,线程池会做如下判断:

1、如果正在运行的线程数量小于coreSize,那么会马上创建线程运行这个任务

2、如果正在运行的线程数量大于或等于coreSize,那么会将这个任务放入阻塞队列中

3、如果这个时候队列满了,且在运行的线程数量还小于maximumPoolSize,那么会创建非核心线程来立刻执行这个任务

4、如果队列满了,且在运行的线程数量大于等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行

jdk内置的拒绝策略(4种):

第一种:AbortPolicy(默认),直接抛异常

第二种:CallerRunsPolicy,不抛弃任务,不抛出异常,而是将某些任务回退到调用方

第三种:DiscardOldestPolicy,抛弃队列中等待最久的任务,然后把当前任务加入到队列中再次尝试提交当前任务

第四种:DiscardPolicy,直接丢弃任务,不处理也不抛异常。如果允许任务丢失,那么这是一种最好的方案

int corePoolSize, 常驻核心数

int maximumPoolSize, 最大核心数

long keepAliveTime, 多余的空闲线程的存活时间,当线程中的线程数量超过corePoolSize时,当空闲时间到达keepAliveTime时,多余

空闲线程会被销毁直到只剩corePoolSize个线程为止

TimeUnit unit, keepAliveTime的单位

BlockingQueue workQueue, 阻塞队列,用于存储多余的任务

ThreadFactory threadFactory, 创建线程的线程工厂,一般默认就行

RejectedExecutionHandler handler, 拒绝策略

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享