JAVA线程以及线程池

一、实现线程方式

1.1 类继承Thread类

    Thread本质上是实现了Runnable接口的实例,自己类继承Thread类,等于重写了Thread对Runnable接口的实现。启动线程的唯一方法就是通过Thread类的start()方法.

/**
 * @Description
 * @Date 2021/07/15 17:11
 * @Created by cc
 */
public class MyThread extends Thread{

    @Override
    public void run(){
        System.out.println("MyThread");
    }

    public static void main(String[] args){
        Thread myThread = new MyThread();

        myThread.start();
    }
}

1.2 实现Runnable接口

    实现Runnable接口,这个在类已经继承了一个类的场景下用的比较多,因为Java的类只能extends一个类。Runnable在运行时,需要占用一个Thread实例,然后通过Thread类的start()方法启动。所以实现代码如下:

/**
 * @Description
 * @Date 2021/07/15 17:16
 * @Created by cc
 */
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("MyRunnable");
    }

    public static void main(String[] args){
        Thread myThread = new Thread(new MyRunnable());

        myThread.start();
    }
}

1.3 实现Callable接口

    需要获取异步线程执行的返回值时,可以实现Callable接口,执行Callable任务后,获取一个Future对象,通过Future的get()方法可以获取异步线程返回值。这里线程会自主检查Future是否都返回,是个阻塞过程,需要等到所有线程任务执行完成,才会继续获取Future之后的代码流程。

public class MyCallable implements Callable<String> {

    private int i;

    public MyCallable(int i){
        this.i = i;
    }

    @Override
    public String call() throws Exception {
        Thread.sleep(1000);
        return "this is thread-"+i;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        FutureTask futureTask = new FutureTask(new MyCallable(10));

        new Thread(futureTask).start();

        System.out.println(futureTask.get());
    }
}

二、线程池

    线程是非常宝贵的资源,每次使用时才创建,使用完就销毁,非常耗时和消耗资源,所以将线程先预先初始化一定数量到内存,即线程池。

    Java里面线程池最顶级的接口是Executor,但是严格意义上讲,Executor并不是一个线程池,而只是一个执行线程的工具,真正的线程池接口是ExecutorService。

    ThreadExectorPool为了使用方便,提供了有四种线程池工厂:CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadExecutor。

2.1 CachedThreadPool

    可缓存的线程池,线程空闲最长时间为60s,无限大的线程池,如果线程池的大小大于执行任务的大小,空闲线程(超过60s)会被回收。比较适合处理时间比较小的任务。

    线程池接口源码如下

public static ExecutorService newCachedThreadPool(){
    return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
}

    通过源码接口,可以看出CachedThreadPool的一些特性:

  • 一个可以无限扩大的线程池,最大为JVM能够承受的最大线程数量
  • corePoolSize为0,即回收线程最小剩余为0
  • keepAliveTime为60s.
  • 采用 SynchronousQueue 队列存储等待任务,这个阻塞队列没有存储空间,所以只要有新任务过来,就必须要新生成线程处理任务。

2.2 FixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

特点分析:

  • 是一种固定大小的线程池
  • corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads
  • keepAliveTime为0,说明线程一旦空闲,就会被回收,但是没有什么用,因为已经固定corePoolSize数量和maximunPoolSize,并且二者相等,所以不会出现空闲线程,多的任务只会在队列中等待。
  • 阻塞队列使用LinkedBlockingQueue,是一个无界队列。
  • 由于阻塞队列是一个无界队列,因此永远不可能拒绝到来的任务。
  • 有采用了无界队列,实际线程数将永远维持在nThreads数量,因此maximunPoolSize和keepAliveTime将没有用途。

2.3 SingleThreadExecutor

public static ExecutorService newSingleThreadExecutor(){
    return new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
  • 只会有一条线程工作
  • corePoolSize为1,
  • 采用无界阻塞队列LinkedBlockingQueue存储等待任务。

2.4 ScheduledThreadPool

    可通过参数设定延迟执行或者定时执行任务的线程池,构造接口可分为两类:仅指定corePoolSize和

(1)仅指定corePoolSize

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
  •    指定核心线程数,corePoolSize
  • maximunPoolSize为无限大
  • 使用DelayedWorkQueue作为等待任务的存储队列,此队列为无界队列。
  • keepAliveTime为0,由于DelayedWorkQueue为无界队列,所以keepAliveTime和maximunPoolSize无效

(2)ScheduledThreadPool实现延迟、定时任务

    ScheduledThreadPool不仅仅继承了ExecutorService类,同时还继承了ScheduledExecutorService,ScheduledExecutorService也继承ExecutorService。在ScheduledExecutorService类中提供了延迟和定时任务的线程任务支持。方法源码接口如下:

  •   schedule(Runnable command,long delay,TimeUnit unit)
public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);

    * command: 实现Runnable接口的类

    * delay:延迟执行时间

    * unit:时间的单位

  • schedule(Callable<String> callable,long delay,TimeUnit unit)
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

    * callable:带返回的线程实现类

    *delay:延迟执行时间

    *unit:时间单位

  • scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
  • scheduleWithFixedDelay(Runnable command,long initialDelay,long period,TimeUnit unit)
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

说明:scheduleAtFixedRate和scheduleWithFixedDelay都是带延迟、定时功能的线程池执行任务接口,区别在于scheduleAtFixedRate只在线程池任务开始时延迟,scheduleWithFixedDelay线程池的每个任务都会延迟。

三、自建线程池

    以上四个线程池的配置,是jdk提供,但是用着很不方便,比如队列无界、最大线程数参数无效、拒绝策略只是默认的抛出异常,阻止系统正常运行。

    自己配置一个线程池,需要定义ThreadExectorPool对象,ThreadExectorPool提供的构造接口如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}
  • corePoolSize:核心线程池数量
  • maxinumPoolSize:最大线程池数量
  • keepAliveTime:线程空闲时间
  • unit:时间单位
  • workQueue:线程任务阻塞队列,后面介绍线程池中使用的队列类型以及功能。
  • treadFactory:线程生成工厂,默认即可。
  • handler:线程任务数量大于maximumPoolSize时,执行拒绝线程任务的操作。

3.1 阻塞队列介绍

3.1.1 ArrayBlockingQueue

    由数组构成的有界阻塞队列,可以设置队列的大小,最大特色是可以设置公平队列,但是设置成公平队列后,会降低吞吐量。第二个参数设置成true,即可实现公平队列。

ArrayBlockingQueue queue1 = new ArrayBlockingQueue(1000,true);

3.1.2 LinkedBlockingQueue

    基于链表的阻塞队列,是最常用的阻塞队列,其能高效的处理并发数据,是因为其在访问者和消费者两端使用独立的锁来保证数据的同步,即生产者和消费者可以同时访问队列。

   LinkedBlockingQueue默认大小为Integer.MAX_VALUE,可主动设置队列大小。    

LinkedBlockingQueue queue2 = new LinkedBlockingQueue(666); 

3.1.3 PriorityBlockingQueue    

    是一个支持优先级的无界队列,默认情况是对元素采取自然顺序生序排列,可自定义compareTo()方法,来指定元素的进行排序规则。

3.1.4 DelayQueue

    支持延迟获取元素的无界阻塞队列。队列中的元素必须实现Delayed接口,在创建元素时,可以指定元素多久才能从队列中出来,只有延迟期满时,才能从队列中获取到元素。

    使用场景:

  • 缓存系统,一旦能从DelayQueue中获取到缓存元素,表示元素的缓存有效期到了。
  • 定时任务,delayQueue存储了定时了任务,一旦能取出定时任务,表示定时任务执行时间到了。ScheduledThreadPool使用了DelayWorkQueue

 3.1.5 SynchronousQueue

    不存储数据队列。

3.1.6 LinkedTranferQueue

    由链表实现的无界阻塞队列。

3.1.7 LinkedBlockingDeque

    链表实现的双向阻塞队列,可以从表头或者表尾拿元素。

    总结:阻塞队列是以上7种,但是在线程池使用时,一般使用到的是LinkedBlockingQueue和SynchronousQueue,LinkedBlockingQueue使用场景很好理解,SynchronousQueue使用场景参照CacheThreadPool,想有任务过来,立马开启线程处理。DelayQueue也可以用作定时任务线程池,但是一般会直接使用Java提供的ScheduledThreadPool工厂获取线程池。

3.2 拒绝策略介绍

    线程池默认的拒绝执行策略是直接抛出错误,阻止程序继续执行,Java还提供了其他拒绝策略供选择,具体如下:

  • AbortPolicy:直接抛出异常,阻止程序继续运行。线程池默认拒绝策略。
  • CallerRunsPolicy:有提交任务线程自己执行,这样会大大影响提交任务线程效率。
  • DiscardOldestPolicy:丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交任务。
  • DiscardPolicy:直接丢弃该任务,不做任何处理。

以上策略都实现了RejectedExecutionHandler接口,如果还不满足要求,可自行定义类实现RejectedExecutionHandler。

四、线程的生命周期

    线程的生命周期分为新建、就绪、运行、阻塞、死亡五个状态。

3.1 新建状态

    使用new关键字创建一个线程后,改线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值。

3.2 就绪状态

    调用了start()方法后,线程处于就绪状体啊,JVM会为其创建方法调用栈合程序计数器,等待调度运行。

3.3 运行状态

    处于就绪状态的线程获得CPU的时间片,开始执行线程的run()方法。

3.4 阻塞状态

    线程由于某种原因放弃了CPU的使用权,即让出了CPU timeSlice,暂时停止运行。知道线程进入就绪(Runnable)状态,才有权利重新获取CPU的时间切片,进而继续运行。

    线程的阻塞状态分为三种:

    等待阻塞,o.wait(),JVM会把线程放到等待队列,等待唤醒。

    同步阻塞,同步锁被其他线程占用而导致的阻塞。

    其他阻塞(sleep/join),运行(Running)状态的线程调用Thread.sleep()方法或者t.join()方法,需要等到sleep()方法超时,活着join的线程终止或者超时,该线程才会重新变成就绪(Runnable)状态,有机会重新申请CPU的时间切片。

3.5 线程死亡

    即线程结束,线程结束的方式分为三种:

    正常结束,run()或call()方法执行完成,线程正常结束。

    异常结束,线程抛出一个未捕获的Exception或者Error

    调用stop,该方法会造成共享数据不一致、损坏的情况,已经废弃。

四、sleep()和wait()

    1、sleep()方法是属于线程Thread的,wait()是属于Object对象的。

    2、sleep()方法使线程暂停执行,交出CPU给其他线程,但是不会释放已经获取的对象锁。

    3、当线程调用wait() 方法时,线程会放弃对象锁,知道锁对象调用notify(),可以重新和其他等待锁定池中的阻塞线程竞争获取对象锁。

五、JAVA的后台线程

    Java的后台线程,又名守护线程,也成为服务线程,是为用户线程提供公共服务的,在没有用户线程可服务时,守护线程也立即离开。

    通过setDaemon(true)方法,设置线程为守护线程。

    比较显著的历史是GC线程,    当程序中不再有其他Thread时,GC线程也会退出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值