JAVA之线程和线程池

一,线程

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。

1 创建线程的方法

1.1 继承Thread类

自定线程类并继承Thread类,重写run方法。创建线程对象,调用start()方法启动线程。

package mian.thread;

public class NewThread  extends Thread{
    public void run(){
        for (int i = 0; i <100 ; i++) {
            System.out.println("我还能卷"+i);
        }
    }
    public static void main(String[] args) {
        NewThread create = new NewThread();
        create.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("我不卷了"+i);
        }
    }

}

1.2 实现Runnable接口

自定义线程类,实现Runnable接口,并重写run方法。创建线程对象,调用start()方法启动线程。

package mian.thread;

public class newThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            System.out.println("继续刷leetcode"+i);
        }
    }

    public static void main(String[] args) {
        newThread thread = new newThread();
        new Thread(thread).start();
        for (int i = 0; i <100 ; i++) {
            System.out.println("刷不死leetcode"+i);
        }
    }
}

1.3 实现Callable接口

实现步骤:

  1. 创建Callable接口的实现类,并重写call()方法,该call()方法将作为线程执行体,并且有返回值。

  2. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。

  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

package mian.thread;

import java.util.concurrent.*;

public class NewThread2 implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println("线程"+Thread.currentThread().getName());
        return "实现callable"+Thread.currentThread().getName();
    }

    public static void main(String[] args) throws InterruptedException, TimeoutException, ExecutionException {
        Callable newThread2 = new NewThread2();
        FutureTask task1 = new FutureTask(newThread2);
        FutureTask task2 = new FutureTask(newThread2);

        new Thread(task1).start();
        new Thread(task2).start();
        Thread.sleep(100);

        System.out.println(task1.get());
        System.out.println(task2.get());

        //get的传入参数为等待时间,超时抛出超时异常;传入参数为空时,则不设超时,一直等待
        System.out.println(task1.get(10L, TimeUnit.MILLISECONDS));
        System.out.println(task2.get(10L, TimeUnit.MILLISECONDS));
    }
}

注:

1. Runnable和Callable

相同点:

1、两者都是接口

2、两者都需要调用Thread.start()启动线程

不同点:

1、如上面代码所示,callable的核心是call方法,允许返回值,runnable的核心是run方法,没有返回值。

2、call方法可以抛出异常,但是run方法不行

3、因为runnable是java1.1就有了,所以他不存在返回值,后期在java1.5进行了优化,就出现了callable,就有了返回值和抛异常

4、callable和runnable都可以应用于executors。而thread类只支持runnable

2. start()和run()的区别

start()方法用来开启线程,一个线程调用start()方法后,此线程处于就绪(可运行)状态,只有得到cpu时间片,才开始执行run()方法。

而run()方法单独讲只是类的一个普通方法而已,如果直接调用Run方法,程序中只有主线程这一个线程,会把 run() 方法当成一个 main 线程下的普通方法去执行,没有达到多线程的目的。

总结: 调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法。这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。

二,线程池

线程池的实现类为ThreadPoolExecutor,其中构造方法有四种,代码如下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

线程池的好处:

  1. 降低资源消耗。重复利用线程
  2. 提高响应速度。省略创建线程的步骤
  3. 提高线程的可管理性。由线程池统一分配,调优和监控

2.1线程池的重要参数

查看上面的代码,可以看到线程池的重要参数有如下几个:

  • corePoolSize:核心线程最大数量,通俗点来讲就是,线程池中常驻线程的最大数量。线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程;如果超过corePoolSize,则新建的是非核心线程。

  • maximumPoolSize:线程池中运行最大线程数(包括核心线程和非核心线程)。

  • keepAliveTime:线程池中空闲线程(仅适用于非核心线程)所能存活的最长时间。当需要执行的任务很多,线程池的线程数大于核心池的大小时,keepAliveTime才起作用。

  • unit:存活时间单位,与keepAliveTime搭配使用(TimeUnit.DAYS,TimeUnit.HOURS,TimeUnit.MINUTES,TimeUnit.MILLISECONDS,TimeUnit.MICRODECONDS)

  • workQueue:存放任务的阻塞队列。维护着等待执行的 Runnable对象,当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务)。

  • handler:线程池饱和策略。

2.1.1 线程池中的任务队列(workQueue)

线程池中的任务队列是基于阻塞队列实现的,即采用生产者消费者模式。Java 为我们提供了 7 种阻塞队列的实现,如下介绍:

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
  • LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE
  • PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
  • **DelayQueue:**类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
  • SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
  • LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
  • LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

**注:**如果使用有界队列,当队列满了并且线程池数超过了最大线程数时就会执行拒绝策略;如果使用无界队列,那么任务队列永远都满不了,那么设置的最大线程数就没用意义了。

2.1.2 线程池中的拒绝策略(handler)

当线程池中,正在运行的线程数量达到了线程池中的最大线程数,并且任务队列也已经阻塞满了的时候,就会去执行拒绝策略,ThreadPoolTaskExecutor 中定义了如下策略:

  • AbortPolicy(默认):抛出 RejectedExecutionException 异常,并丢弃新任务的处理。
  • CallerRunsPolicy: 调用执行自己的线程运行任务,也就是直接在调用execute方法中的线程运行被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。
  • DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
  • DiscardOldestPolicy:丢弃任务队列中最早的未处理任务。

2.2 线程池的执行流程

下面用一张流程图来藐视一下线程池的工作原理,让自己和大家对线程池参数的作用有一个总体的把握。如下:

在这里插入图片描述

2.3 创建线程池

2.3.1 工具类 Executors封装好的线程池

1,SingleThreadExecutor(单线程池)

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。单线程池等同于传入参数为1的FixedThreadPool,代码如下:

package mian.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *@Author wjq
 *@Date 2022/5/21
 *@Version v1.0
 *@Description 单线程池
 */
public class createSingle {
    public static void main(String[] args) {

        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
    }
}

查看线程池源代码:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

特点 :核心线程数和最大线程数相等都为1。线程没有存活时间,执行完任务就被回收,任务队列为LinkedBlockingQueue。其不适合并发操作。

2,FixedThreadPool(固定大小线程池)

需要指定线程池的大小,创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。代码如下:

package mian.thread;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *@Author wjq
 *@Date 2022/5/20
 *@Version v1.0
 *@Description 定长线程池
 */
public class creatFixed {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
    }
}

查看线程池源代码:

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

特点 :需要传入参数线程池中线程的个数nThreads,即核心线程数和最大线程数相等都为nThreads。线程没有存活时间,执行完任务就被回收,任务队列为LinkedBlockingQueue。

3,CachedThreadPool(可缓存线程池)

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

package mian.thread;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *@Author wjq
 *@Date 2022/5/20
 *@Version v1.0
 *@Description 可缓存线程池
 */
public class creatCached {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i <100 ; i++) {
            int index =i;
            Thread.sleep(100);
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("ThreadName:"+Thread.currentThread().getName()+",========index="+index);
                }
            });

        }
    }
}

线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

查看线程池源码:

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

特点:核心线程数为0,最大线程数为Integer.MAX_VALUE,线程存活时间为60L,任务队列为SynchronousQueue。

4,ScheduledThreadPool(定时线程池)

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

package mian.thread;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 *@Author wjq
 *@Date 2022/5/21
 *@Version v1.0
 *@Description 定长线程池
 */
public class createScheduled {
    public static void main(String[] args) {

        ScheduledExecutorService service = Executors.newScheduledThreadPool(2);

        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        }, 5, TimeUnit.SECONDS);

        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        }, 5, 2, TimeUnit.SECONDS);

    }
}

线程池源代码如下:

private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
 
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}
 
public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue(), threadFactory);
}

特点:相比前面三种,顶时线程池的源码较为复杂,其核心线程数需要手动传入,最大线程数为

Integer.MAX_VALUE,线程存活时间为10L,任务队列为DelayedWorkQueue。

5,四种线程池对比
类型线程类型线程数量适用场景
SingleThreadExecutor(单线程池)核心线程core:1 max:1单线程
FixedThreadPool(固定大小线程池)核心&非核心线程core:设定 max:设定控制线程最大并发数
CachedThreadPool(可缓存线程池)非核心线程core:0 max:无限制执行数量多,耗时少的任务
ScheduledThreadPool(定时线程池)核心&非核心线程core:设定max:无限制定时/周期性任务

2.3.2 使用 ThreadPoolExecutor 构造函数自定义参数

自定义线程示例代码:

package mian.thread;

import java.util.Date;

/**
 *@Author wjq
 *@Date 2022/5/21
 *@Version v1.0
 *@Description 自定义线程
 */
public class DiyThread implements Runnable {
    private String command;

    public DiyThread(String s) {
        this.command = s;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
        processCommand();
        System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
    }

    private void processCommand() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return this.command;
    }
}

自定义线程池示例代码:

package mian.thread;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 *@Author wjq
 *@Date 2022/5/21
 *@Version v1.0
 *@Description 自定义线程池
 */
public class creatDiyThread {
    private static final int core_pool_size=5;
    private static final int max_poll_size =10;
    private static final int queue_capacity=100;
    private static final Long keep_alive_time=0L;

    public static void main(String[] args) {
        //通过ThreadPoolExecutor构造函数自定义参数创建
        ThreadPoolExecutor myexecutor = new ThreadPoolExecutor(core_pool_size,
                max_poll_size,
                keep_alive_time,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(queue_capacity),
                new ThreadPoolExecutor.DiscardOldestPolicy());
        for (int i = 0; i <20 ; i++) {
            Runnable worker = new DiyThread("" + i);
            myexecutor.execute(worker);
        }
        myexecutor.shutdown();
        while (!myexecutor.isTerminated()){
        }
        System.out.println("Finished all threads");
    }
}

说明:我们自定义的线程池参数为:

corePoolSize5
maximumPoolSize10
keepAliveTime0L(立即回收)
unitTimeUnit.SECONDS
workQueue任务队列为 ArrayBlockingQueue,并且容量为 100;
handlerThreadPoolExecutor.DiscardOldestPolicy

总结:

  1. Executors 的 4 个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理方式更加适用于实际需要场景,规避资源耗尽的风险。
  2. 由于可缓存线程池和定时线程池的最大线程数为Integer.MAX_VALUE,所以当大量任务一下涌入时,可能会一下子创建大量线程,造成OOM。

参考:

1.Java 多线程:彻底搞懂线程池-孙强 Jimmy

2.JavaGuide-Guide哥

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王小二_Leon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值