线程的一些知识

本文详细介绍了Java中通过继承Thread类、实现Runnable接口、Callable/Future和线程池创建线程的方法,以及线程的生命周期和线程池的使用、配置与线程池大小选择的策略。
摘要由CSDN通过智能技术生成

线程的创建(常见方式)

1.继承Thread类创建线程
2.实现Runnable接口创建线程
3.使用Callable和Future创建线程 有返回值
4.使用线程池创建线程

import java.util.concurrent.*;

public class threadTest{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //继承thread
        ThreadClass thread = new ThreadClass();
        thread.start();
        Thread.sleep(100);
        System.out.println("#####################");

        //实现runnable
        RunnableClass runnable = new RunnableClass();
        new Thread(runnable).start();
        Thread.sleep(100);
        System.out.println("#####################");

        //实现callable
        FutureTask futureTask = new FutureTask(new CallableClass());
        futureTask.run();
        System.out.println("callable返回值:" + futureTask.get());
        Thread.sleep(100);
        System.out.println("#####################");

        //线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
        threadPoolExecutor.execute(thread);
        threadPoolExecutor.shutdown();
        Thread.sleep(100);
        System.out.println("#####################");

        //使用并发包Executors
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.execute(thread);
        executorService.shutdown();
    }
}

class ThreadClass extends Thread{
    @Override
    public void run() {
        System.out.println("继承thread形式:" + Thread.currentThread().getName());
    }
}

class RunnableClass implements Runnable{
    @Override
    public void run(){
        System.out.println("实现runnable接口:" + Thread.currentThread().getName());
    }
}

class CallableClass  implements Callable<String> {
    @Override
    public String call(){
        System.out.println("我是实现callable接口:");
        return "返回值,可以通过get方法获取";
    }
}

线程 状态(生命周期)

在这里插入图片描述
1.新建状态(New) :线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2.就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3.运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

1.等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
3.其他阻塞 -- 通过调用线程的sleep()join()或发出了I/O请求时,线程会进入到阻塞状态。
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程池,线程池创建

线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率

ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();

这4种线程池底层 全部是ThreadPoolExecutor对象的实现,阿里规范手册中规定线程池采用ThreadPoolExecutor自定义的,实际开发也是。
1.newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这种类型的线程池特点是:
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
2.newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
3.newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
4.newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行。例如延迟3秒执行。

为什么要使用线程池?

线程池做的工作主要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最 大数量,超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
主要特点:
线程复用 控制最大并发数 管理线程
第一:降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进 行统一的分配,调优和监控

线程池底层工作原理

在这里插入图片描述
1.第一步:线程池刚创建的时候,里面没有任何线程,等到有任务过来的时候才会创建线程。当然也可以调用 prestartAllCoreThreads() 或者 prestartCoreThread() 方法预创建corePoolSize个线程
2.第二步:调用execute()提交一个任务时,如果当前的工作线程数<corePoolSize,直接创建新的线程执行这个任务
3.第三步:如果当时工作线程数量>=corePoolSize,会将任务放入任务队列中缓存
4.第四步:如果队列已满,并且线程池中工作线程的数量<maximumPoolSize,还是会创建线程执行这个任务
5.第五步:如果队列已满,并且线程池中的线程已达到maximumPoolSize,这个时候会执行拒绝策略,JAVA线程池默认的策略是AbortPolicy,即抛出RejectedExecutionException异常

线程池 状态

在这里插入图片描述

  1. RUNNING(运行中):线程池处于正常运行状态,可以接受新任务并处理已提交的任务。
  2. SHUTDOWN(关闭中):线程池不再接受新任务,但会继续处理已提交的任务,直到任务队列为空。shutdown()方法用于将线程池状态切换为SHUTDOWN。
  3. STOP(停止中):线程池不再接受新任务,并且会尝试终止正在执行的任务。已提交但未执行的任务会从队列中移除。shutdownNow()方法用于将线程池状态切换为STOP。
  4. TIDYING(整理中):线程池在SHUTDOWN或STOP状态下,当所有任务都已经终止,工作线程数为0时,会将线程池状态切换为TIDYING,表示线程池正在进行一些清理工作。
  5. TERMINATED(终止):线程池的终止状态,表示线程池已经完全终止,不再处理任务。线程池状态会在TIDYING状态结束后切换到TERMINATED。

ThreadPoolExecutor对象有哪些参数

1.corePoolSize:核心线程数
在ThreadPoolExecutor中有一个与它相关的配置:allowCoreThreadTimeOut(默认为false),当allowCoreThreadTimeOut为false时,核心线程会一直存活,哪怕是一直空闲着。而当allowCoreThreadTimeOut为true时核心线程空闲时间超过keepAliveTime时会被回收。
2.maximumPoolSize:最大线程数
线程池能容纳的最大线程数,当线程池中的线程达到最大时,此时添加任务将会采用拒绝策略,默认的拒绝策略是抛出一个运行时错误(RejectedExecutionException)。值得一提的是,当初始化时用的工作队列为LinkedBlockingDeque时,这个值将无效。
3.keepAliveTime:存活时间
当非核心空闲超过这个时间将被回收,同时空闲核心线程是否回收受allowCoreThreadTimeOut影响。
4.unit:keepAliveTime的单位。
5.workQueue:任务队列
常用有三种队列,即SynchronousQueue,LinkedBlockingDeque(无界队列),ArrayBlockingQueue(有界队列)。
6.threadFactory:线程工厂
ThreadFactory是一个接口,用来创建worker。通过线程工厂可以对线程的一些属性进行定制。默认直接新建线程。
7.RejectedExecutionHandler:拒绝策略
也是一个接口,只有一个方法,当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution法。默认是抛出一个运行时异常。
拒绝策略:
1.AbortPolicy:直接抛出异常,默认策略;
2.CallerRunsPolicy:用调用者所在的线程来执行任务;
3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4.DiscardPolicy:直接丢弃任务;当然也可以根据应用场景实现
RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务

线程池大小如何设置

CPU密集型:N+1 IO密集型:2N

1.需要分析线程池执行的任务的特性: CPU 密集型还是 IO 密集型
2.每个任务执行的平均时长大概是多少,这个任务的执行时长可能还跟任务处理逻辑是否涉及到网络传输以及底层系统资源依赖有关系
如果是 CPU 密集型,主要是执行计算任务,响应时间很快,cpu 一直在运行,这种任务 cpu的利用率很高,那么线程数的配置应该根据 CPU 核心数来决定,CPU 核心数=最大同时执行线程数,加入 CPU 核心数为 4,那么服务器最多能同时执行 4 个线程。过多的线程会导致上下文切换反而使得效率降低。那线程池的最大线程数可以配置为 cpu 核心数+1 如果是 IO 密集型,主要是进行 IO 操作,执行 IO 操作的时间较长,这是 cpu 出于空闲状态,导致 cpu 的利用率不高,这种情况下可以增加线程池的大小。这种情况下可以结合线程的等待时长来做判断,等待时间越高,那么线程数也相对越多。
一般可以配置 cpu 核心数的 2 倍。一个公式:线程池设定最佳线程数目 =

((线程池设定的线程等待时间+线程 CPU 时间)/ 线程 CPU 时间 ) CPU 数目*

这个公式的线程 cpu 时间是预估的程序单个线程在 cpu 上运行的时间(通常使用 loadrunner测试大量运行次数求出平均值)

  • 26
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值