栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA多线程-线程池

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA多线程-线程池

线程池的主要工作

线程池做的工作主要是控制运行的线程的数量。处理过程中将任务放入队列,然后在线程创建之后启动这些任务。如果线程数量超过了最大数量,超出数量的线程排队等待。等待其它线程执行完毕之后,再从队列中取出任务来执行。 特点 线程复用

减低了资源的消耗。通过复制利用已经创建的线程降低线程创建和销毁造成的资源消耗。 控制最大并发数

提高响应速度。当任务到达时,任务可以不需要等到线程创建就可以立即执行。 管理线程

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

ExecutorService 接口继承了 Executor 接口。我们一般用 Executor 接口的池化对象来进行多态。就像我们创建 ArrayList 来多态 List接口 而不是 Collection 接口。 架构说明

|

继承关系
编码实现

下边几种,测试线程池用用就行了。

一般自己用构造函数初始化一个线程池。

固定线程数

可控制线程最大并发数,超出的线程会在队列中等待。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new linkedBlockingQueue());
}
Executors.newFixedThreadPool(int) // 执行长期的任务来说的话性能会好很多
public class ThreadPoolDemo01 {

    public static void main(String[] args) {
        // ExecutorService 接口继承了 Executor 接口
        // 我们一般用 Executor 接口的池化对象来进行多态
        // 就像我们创建 ArrayList 来多态 List接口 而不是 Collection 接口
        // 一池五个处理线程
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        
        // 模拟十个用户来办理业务
        // 每个用户就是一个来自外部,来请求线程
        try {
            // 使用
            for (int i = 0; i < 10; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + 
                                       "线程t 办理业务。。。");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭
            threadPool.shutdown();
        }
    }
}

 

最多就五个线程在复用满足外部的十个业务请求。 一池一线程

创建一个单线程化的线程池。它只会用唯一的工作线程来执行任务,保证所有任务按照指定的顺序执行。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new linkedBlockingQueue()));
}
Executors.newSingleThreadExecutor() // 一个任务一个任务的执行场景,唯一的工作线程来执行任务,保证所有任务按照指定的顺序执行
package JUC.threadpool;

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


public class ThreadPoolDemo01 {

    public static void main(String[] args) {
        // 一个任务一个线程,一池一线程
        ExecutorService threadPool = Executors.newSingleThreadExecutor();

        try {
            for (int i = 0; i < 10; i++) {
                threadPool.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + 
                                       "线程t 办理业务。。。");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

 

我们可以看出这个线程池中只有一个线程在被使用。 一池多线程

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

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  // 来了任务就创建线程运行,当线程空闲超过60s就给他销毁
                                  new SynchronousQueue());
}
Executor.newCachedThreadPool() // 适用:执行很多短期异步的小程序或者负载较轻的服务器
public void autoThreadPool() {
    // 一池多线程,数量会调整
    ExecutorService threadPool = Executors.newCachedThreadPool();

    try {
        for (int i = 0; i < 16; i++) {
            threadPool.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "线程t 办理业务。。。");
            });
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        threadPool.shutdown();
    }
}
 

线程池中的数量是随请求使用线程的次数来定的。我的电脑最多一个线程池10个线程。 ThreadPoolExecutor类 (重点) 核心线程数

核心线程会一直存在,即使没有任务。 七大参数(重点)

// 线程池中的常驻核心线程数
// 当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列中去
int corePoolSize

// 线程池能够容纳“同时执行的最大线程数”,此值必须大于1
int maximumPoolSize

// 多余的空余线程的存活时间
// 当线程池数量超过 corePoolSize 时,
// 同时当空闲时间达到 keepAliveTime 时,
// 多余的线程会被销毁,直到只剩下 corePoolSize 个线程为止
long keepAliveTime
// keepAliveTime的单位
TimeUnit unit
    
// 任务队列,被提交但是未被执行的任务,也就是阻塞队列
BlockingQueue workQueue
    
// 表示生成线程池中工作线程的线程工厂
// 用于创建线程,一般用默认的即可
ThreadFactory threadFactory
    
// 拒绝策略。表示当队列满了并且工作线程数大于等于线程池的最大线程数 maxmumPoolSize 时,
// 如何来拒绝任务
RejectedExecutionHandler handler
构造函数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue 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.acc = System.getSecurityManager() == null ?
        null :
    AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
线程池工作原理(重要)

对应图中 1、2、3、4 四个步骤:

如果核心线程使用数量到了 corePoolSize,那么新的任务先放阻塞队列里,如果阻塞队列满了,那么就增加核心线程数,此时增加的核心线程数不是指常驻的核心线程,是指临时的核心线程,当增加的临时核心线程数等于 maximumPoolSize 的时候,就不再往线程池中添加新的临时线程。如果此时阻塞队列和线程池线程数量都达到阈值,并且还源源不断的有新的请求,此时就触发拒绝机制。最后等到阻塞队列为空,线程池中没有线程有要处理的代码段,此时我们判断临时核心线程的等待时间是否等于 keepAliveTime,如果等于,那就销毁临时核心线程。最后我们处理完一切事物,就==只留下了空空的阻塞队列以及我们线程池的两个常驻核心线程==。

|

图示
拒绝策略(重要)

以下四个JDK内置的拒绝策略均实现了 RejectedExecutionHandler 接口。

AbortPolicy(默认)

直接抛出 RejectedExecutionException 异常。

阻止系统正常运行。

private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

CallerRunsPolicy

**“调用者运行”**机制,是一种调节机制。该策略既不会抛弃任务,也不会抛出异常。它会将某些任务回退给调用者,从而降低新任务的流量。

DiscardOldestPolicy

抛弃队列中等待最久的任务。然后把当前任务加入到队列中尝试再次提交当前任务。

DiscardPolicy

直接丢弃该任务。不处理,不抛异常,如果允许任务丢失的话,这是最好的一种方案。 自定义线程池案例 未使用自定义拒绝策略 AbortPolicy版

public class CustomThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(2,5
                                                            ,60L, TimeUnit.MILLISEConDS
                                                            , new linkedBlockingQueue(3)
                                                            , Executors.defaultThreadFactory()
                                                            ,new ThreadPoolExecutor.AbortPolicy());

        try {
            // 此时阻塞队列和线程池最大长度为 8,同时并发超过 8 的话就会造成拒绝
            for (int i = 0; i < 9; i++) {
          //for (int i = 0; i < 8; i++) { // 不会被拒绝
                // 并发 9
                threadPool.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + "线程t 处理业务!");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}
 
CallerRunsPolicy版 
public class CustomThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(2,5
                ,60L, TimeUnit.MILLISEConDS
                , new linkedBlockingQueue(3)
                , Executors.defaultThreadFactory()
                ,new ThreadPoolExecutor.CallerRunsPolicy());

        try {
            // 超出线程池允许最大并发度
            for (int i = 0; i < 9; i++) {
                threadPool.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + "线程t 处理业务!");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}
 

其它俩个慎用。 使用自定义拒绝策略 如何合理的配置线程池线程数 CPU密集型

CPU密集的意思是:

该任务需要大量的运算,如果没有阻塞,CPU一直全速运行。CPU的密集任务只有在真正的多核CPU上才可能得到加速(通过多线程)。所以单核CPU就别想加速了,再怎么加速也只有一个核心,最快也就一个核心的算力。

先查看我们的CPU核心数,再进一步去设置 corePoolSize 的大小。

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

一般我们的CPU密集型任务配置尽可能少的线程数量。

公式大概是:CPU核心数+1个线程的线程池。

IO密集型

即该任务需要大量的IO,即大量的阻塞。

单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。

所以在IO密集型任务中使用多线程可以大大的加速程序的运行。

在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间,所以也能有加速效果。

因为IO密集型的任务线程不一定一直在执行任务。

则应该配置尽可能多的线程。

一、CPU核数 * 2二、CPU核数 / (1 - 阻塞系数) (阻塞系数在0.8~0.9) 死锁 是什么?

是指两个或者两个以上的线程或进程在执行的过程中,因抢夺资源而造成的一种互相等待的现象。造成这种现象后如果没有外力干涉那么就无法进行下去,这就是死锁。如果系统资源充足,线程或进程的资源请求都能满足,死锁出现的可能性就会很低,而不是不会出现。

|

图示
造成原因

系统资源不足。进程运行推进顺序不合适。资源分配不当。请求并持有资源,互斥,不可中断,循环等待。 编码

package JUC.deadlock;

import java.util.concurrent.TimeUnit;



class HoldThread implements Runnable{

    private String lock1;
    private String lock2;

    public HoldThread(String lockA, String lockB) {
        this.lock1 = lockA;
        this.lock2 = lockB;
    }

    @Override
    public void run() {
        // 可重入的前提是同一个锁,所以这里不是可重入锁
        synchronized (lock1) {
            System.out.println(Thread.currentThread().getName() + "线程t 持有" + lock1 + "t正在尝试获得" + lock2);
            
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            synchronized (lock2) {

            }
        }
    }
}

public class DeadLockDemo {

    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";

        new Thread(new HoldThread(lockA,lockB),"A").start();
        new Thread(new HoldThread(lockB,lockA),"B").start();
    }
}
 

字符串是常量,所以说,两个线程抢的锁是相同的两把锁。如果是别的引用的话,这里的锁就是4把不同的锁,那么例子就出错。而且可能出现,A线程已经全部运行完了获得了两把锁,B线程一把锁都没获得的情况,也可能反过来。 定位分析

linux:

ps -ef|grep xxxx

windows:

windows下的java程序运行,也有类似linux下的查看进程的ps命令,但是目前我们只看java。jps.exe = java ps 可以用 jps -ljps -l 查看正在运行的java程序,找到可疑进程的PID。jstack PID 查看当前进程的异常栈,有没有报出死锁。

Java stack information for the threads listed above:
===================================================
"B":
        at JUC.deadlock.HoldThread.run(DeadLockDemo.java:35)
        - waiting to lock <0x00000007160d5f48> (a java.lang.String)
        - locked <0x00000007160d5f80> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)
"A":
        at JUC.deadlock.HoldThread.run(DeadLockDemo.java:35)
        - waiting to lock <0x00000007160d5f80> (a java.lang.String)
        - locked <0x00000007160d5f48> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.
D:DevelopmentEnvironmentGitGitRepositoryjavase-learning>

码云仓库同步笔记,可自取欢迎各位star指正:https://gitee.com/noblegasesgoo/notes

如果出错希望评论区大佬互相讨论指正,维护社区健康大家一起出一份力,不能有容忍错误知识。
										—————————————————————— 爱你们的 noblegasesgoo
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/732281.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号