【Java】线程、线程池

线程

什么是线程

线程是调度CPU的最小单位。线程模型分为内核级模型(KLT)和用户级模型(ULT)。JVM使用的是内核级模型(KLT)。Java线程和OS线程是保持1:1的映射关系, 也就是说有一个java线程也会在操作系统里有对应的线程。

线程的生命状态

java线程有多种生命状态 :

  • NEW 初始化状态/新建
  • RUNNABLE 运行状态
  • BLOCKED 阻塞
  • WAITING 等待
  • TIME_WAITING 超时等待
  • TERMINATED 终结

线程的创建

可以通过构建一个Thread的子类来定义一个线程,然后构建一个子类的对象,并调用start方法。不要直接调用run方法,run方法只会执行同一个线程中的任务,而不会启动新线程。调用start方法,会创建一个执行run的新线程。

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("创建了一个线程!!");
    }
}

public class ThreadMain {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

我们可以看到Thread类也是实现了Runnable接口,那么我们是不是也可以通过实现Runnable接口创建线程呢?
在这里插入图片描述
当然是可以的,具体代码示例展示如下:

public class MyThread2 implements Runnable{
    @Override
    public void run() {
        System.out.println("实现runnable接口创建线程!!");
    }
}

public class ThreadMain {
    public static void main(String[] args) {
        MyThread2 myThread2 = new MyThread2();
        Thread thread = new Thread(myThread2);
        thread.start();
    }
}

除此之外还可以通过实现定义Callable接口,重写call()方法。FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。

public class MyThread3 implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println("执行一个线程!!!");
        return Thread.currentThread().getName();
    }
}

public class ThreadMain {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread3 myThread3 = new MyThread3();
        FutureTask futureTask = new FutureTask(myThread3);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("结果为:"+ futureTask.get());

    }
}

总结
1、由于java是单继承多实现,所以继承Thread对象的类无法在继承其他类。
2、通过实现Runnable、Callable接口创建线程的方式,线程类还可以继承其他类;适合多个线程处理同份资源,将cpu、代码、数据分开;
3、Runnable规定必须实现run方法,且无法抛出异常。Callable规定实现call方法,能够抛出异常。Callable执行完任务后,可以返回结果。

线程池

通过对线程的介绍,我们可以看到,若是对每一个任务都创建一个线程,那么将会频繁的消耗系统资源。面对这样的问题,Java提供了线程池来解决这个问题。

什么是线程池?

顾名思义,线程池就是一个线程缓存,线程是稀缺资源,如果被无限制创建,不仅会消耗系统资源,还会降低系统稳定性。因此java种提供线程池对线程统一分配、调优和监控。

什么时候使用线程池?

· 单个任务处理执行时间较短
· 需要处理的任务数量很大

线程池的优势?

重用已存在的线程,减少线程的创建、消亡的开销,从而提高性能。 提高响应速度,当任务到达时,无需创建线程就能立即执行. 提高线程的可管理性。

线程池的实现

Executors创建线程池存在4种方法:

  1. newFixedThreadPool(int nThreads) — 创建固定数目线程的线程池
  2. newCachedThreadPool() – 创建一个缓存线程池
  3. newSingleThreadExecutor() – 创建一个单线程化的Executor
  4. newScheduledThreadPool(int corePoolSize) – 创建一个定时及周期性的执行的线程池

但是不建议使用Executors创建线程池
是因为使用Executors创建线程池存在OOM(OutOfMemoryError堆内存不存)的风险。原因:不设置容量,底层是一个无边界的阻塞队列,默认长度未Integer.MAX_VALUE。对于一个无边界队列来说,是可以不断地向队列种加入任务的,任务过多就可能导致OOM。

如何正确创建线程池?

避免使用Executors创建线程池,主要是避免使用其中的默认实现。推荐使用ThreadPoolExecutor类的构造方法自己创建线程池,在创建的同时,给BlockingQueue指定容量就可以了
下面介绍一下

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)                               

每个参数代表的含义如下:

  • corePool

线程池中的核心线程数 ,当提交一个任务时,线程池创建一个新的线程执行任务,直到当前线程数等于corePoolSize;
如果当前线程数为corePoolSize,继续提交的任务将被保存到阻塞队列中,等待执行。
如果执行了线程的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

  • maximumPoolSize

线程池中允许的最大线程数。
如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSizw

  • keepAliveTime

线程池维护线程所容许的空闲时间。
当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程数外的线程不会立即被销毁,而是会等待,直到等待时间超过了keepAliveTime;

  • unit

keepAliveTime的单位

  • workQueue

用来保存等待执行任务的阻塞队列,且任务必须实现Runnable接口。
JDK中提供了如下阻塞队列:
ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务。

  • LinkedBlockingQueue:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常高于ArrayBlockingQueue;···
  • threadFactory

他是ThreadFactory类型的变量,用来创建线程。默认使用Executors.defaultThreadFactory()来创建线程。
使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级,并且是非守护线程,同时也设置了线程的名称。

  • handler

线程池的饱和(拒绝策略)策略,当阻塞队列满了,且没有空闲的工作线程,如过想继续提交任务,必须采取一种策略处理该任务。

示例代码如下:

public class MyThreadPoolExecutor {
    public static void main(String[] args) {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,100,1000,
                TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 20; i++) {
            // 创建任务对象
            Runnable task = () -> System.out.println(Thread.currentThread().getName() + ":任务执行啦");
            threadPoolExecutor.execute(task);
        }
        // 关闭线程池
        threadPoolExecutor.shutdown();
    }
}

线程池的执行流程?

  • 如果workerCount(工作线程)<corePoolSize,则创建并启动一个线程来执行新提交的任务。
  • 如果workerCount(工作线程)>=corePoolSize,且线程池内的阻塞队列未满,则将任务添加到阻塞队列中。
  • 如果workerCount(工作线程)>=corePoolSize && workerCount(工作线程) < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
  • 如果 workerCount(工作线程) >= maximumPoolSize,且线程池内的阻塞队列已满,则根据拒绝策略来处理该任务,默认处理方式是抛异常。

如有错误感谢指正

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值