java并发编程 第六期 线程池

多线程的使用

继承Thread ,实现Runable,实现Callable 传入FutureTask中

package com.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

class MyThread extends Thread{
    @Override
    public void run() {
       System.out.println("继承Thread实现多线程"+Thread.currentThread().getName());
    }
}

class MyRunable implements Runnable{
    @Override
    public void run() {
        System.out.println("实现Runnable实现多线程"+Thread.currentThread().getName());
    }
}

class MyCallable implements Callable<String> { 
    @Override
    public String call() throws Exception {
        TimeUnit.SECONDS.sleep(3);
        System.out.println("实现MyCallable实现多线程"+Thread.currentThread().getName());
        return "callAble";
    }
}
/**
 * @author liuxu
 * @date 2021/11/14 19:53
 */
public class ThreadDemo {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("AA");
        myThread.start();
        Thread thread = new Thread(new MyRunable());
        thread.setName("BB");
        thread.start();
		//FutureTask实现了Runable 所以和Runable一样使用只不过可以拿到返回值,阻塞返回
        FutureTask<String> futureTask = new FutureTask(new MyCallable());
        FutureTask<String> futureTask1 = new FutureTask(new MyCallable());
        Thread thread1 = new Thread(futureTask);
        Thread thread2 = new Thread(futureTask1);
        thread1.start();
        thread2.start();
        try {
            String s = futureTask.get();
            String s1 = futureTask1.get();


            System.out.println(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

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

主要特点是: 线程复用,控制最大并发数,管理线程

优点:

降低资源消耗 通过复用降低了线程创建和销毁造成的消耗

提高响应速度 任务到达时,如果有空闲线程不需要创建线程就可以立即执行

提高线程的可管理性 可以对线程资源统一分配,优化和监控。

线程池的相关接口

常用线程池的使用方法

一共五个

  1. //创建单核心的线程池
  2. ExecutorService executorService = Executors.newSingleThreadExecutor();
  1. //创建固定核心数的线程池,这里核心数 = 2
  2. ExecutorService executorService = Executors.newFixedThreadPool(2);
  1. //创建一个按照计划规定执行的线程池,这里核心数 = 2
  2. ExecutorService executorService = Executors.newScheduledThreadPool(2);
  1. //创建一个自动增长的线程池
  2. ExecutorService executorService = Executors.newCachedThreadPool();
  1. //创建一个具有抢占式操作的线程池
  2. ExecutorService executorService = Executors.newWorkStealingPool();

下面是常用案例

package com.thread;

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

/**
 * @author liuxu
 * @date 2021/11/14 21:29
 */
public class ExcutorDemo {
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);//固定大小线程池 适用长期任务
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();//单个线程 适用一个一个任务执行的场景
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); //大小自适应  适用多短期异步小程序或者负载比较轻的服务器
        try{
            for (int i = 0; i < 10; i++) {
               /*fixedThreadPool.execute(()->{
                    //实现Ranable的内部类
                    System.out.println(Thread.currentThread().getName()+"\t 固定线程数线程办理业务");
                });*/
                /*singleThreadExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 单线程办理业务");
                });*/

                cachedThreadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 自适应大小单线程办理业务");
                });
            }


        }catch (Exception e){

            e.printStackTrace();
        }finally {
            //fixedThreadPool.shutdown();
            // singleThreadExecutor.shutdown();
            cachedThreadPool.shutdown();
        }
    }
}

底层原理

ExecutorService构造 可以看出5大参数

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


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


//点进去可以看到7个参数
    public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数
                              int maximumPoolSize,//能够容纳同时执行的线程数,此值必须大于1
                              long keepAliveTime,//多余的空闲线程的存活时间
                              TimeUnit unit,// 存活时间的时间单位
                              //当线程数超过corePoolSize 有空闲线程且空闲线程存活时间大于keepAliveTime,
                              //空闲线程就会销毁 直到线程数等于 corePoolSize
                              BlockingQueue<Runnable> workQueue, //任务队列,存放已提交但是未被执行的任务
                              ThreadFactory threadFactory,//创建线程的工厂,用默认的的就行
                              RejectedExecutionHandler handler) //拒绝策略,
        							队列满切工作线程大于maximumPoolSize时提交的任务的处理方式

调整线程池的7个参数可以设置不同的线程工作状态

状态1

添加的任务小于核心线程数 线程池中线程功能数

状态

添加任务个数

线程池中运行的线程个数

阻塞队列中任务

说明

状态1

<corePoolSize

corePoolSize

此时来任务会马上调用创建好的核心线程执行任务

状态2

>=corePoolSize

>corePoolSize

此时新加任务会被放入队列,如果有线程执行任务完毕,将从队列中拉取任务执行,此时无空闲线程,

状态3

队列已满且运行线程数量<maximumPoolSize

corePoolSize +

有且已经最大

线程池会扩容但是小于maximumPoolSize,立即执行新添加的任务
此时无空闲线程,

状态4

队列已满且运行线程数量=maximumPoolSize

maximumPoolSize

有且已经最大

拒绝策略

当线程完成任务时,他会从队列中获取一个任务

package com.thread;

import java.util.concurrent.*;

/**
 * @author liuxu
 * @date 2021/11/15 21:22
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        /**
         *如输出展示
         * 8个任务进来
         * 1 2被核心线程接受处理
         * 3 4 5 等待区等待
         * 6 7 8 任务进来时队列满座
         * 新开3个线程达到最大线程 处理 6 7 8
         * 最后 先完成任务的线程 再从队列中拉取 3 4 5执行
         */
        ExecutorService service = new ThreadPoolExecutor(2, 5,
                100L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        try {
            for (int i = 1; i <= 8; i++) {
                int tem = i;
                service.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "线程处理任务" + tem);
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });

            }
        } finally {
            service.shutdown();
        }
    }
}

线程池工作原理总结

1.创建线程池,大小为核心线程池大小,等待任务 Runable接口来临

2.任务来临,调用execute方法,做如下判断

2.1 如果当前运行的线程数小于核心线程数,立即使用创建好的核心线程执行任务

2.2 如果任务大于等于核心线程数,新来的任务放入队列

2.3 如果此时队列已满,且线程数小于最大线程数,立即创建线程执行任务

2.4 如果队列已满且线程数小于等于最大线程数,执行拒绝策略

3.当一个线程空闲达到一定时间,会做出判断

如果线程大于核心线程数,这个线程会被停止

所有任务完成后,线程池会缩减为核心线程池大小

线程池的拒绝策略

JDK内置的拒绝策略有四种 都继承了RejectedExecutionHandler

new ThreadPoolExecutor.AbortPolicy() //直接 抛出异常

new ThreadPoolExecutor.CallerRunsPolicy()//一种调节机制 ,建将任务返回给调用线程

new ThreadPoolExecutor.DiscardOldestPolicy());// 丢弃队列中等待最久的任务

new ThreadPoolExecutor.DiscardPolicy());//新来任务直接丢弃

可以在上面的代码中将最后一个参数替换后尝试效果

工作中实际上使用哪一个线程池

new ThreadPoolExecutor()明确的指定参数

如何合理配置线程池大小

CPU密集型 :CPU核数+1 while (true)

IO秘籍型 读取数据频繁 ,读取文件频繁: CPU核数*2 或者 CPU核数/(1-阻塞系数)

阻塞系数在0.8-0.9之间 即 10-12.5倍的 CPU核数

死锁

现象:线程A 持有锁A实体获取锁B 线程B持有锁B尝试获取锁A

原因:系统资源不足,进程推进顺序 不合适,资源分配不当

死锁代码展示

package com.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class HoldThread implements Runnable{
    private String lockA;
    private String lockB;

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

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+"持有"+lockA+"尝试获取"+lockB);
            try {
                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "持有" + lockB + "尝试获取" + lockA);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}
/**
 * @author liuxu
 * @date 2021/11/16 20:42
 */
public class deadLockDemo {


    public static void main(String[] args) {


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


        Thread thread = new Thread(new HoldThread("B", "A")
                , "B");
        thread.start(); //死锁
        /*try { //非死锁
            TimeUnit.SECONDS.sleep(3);
            thread.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
    }
}

查找死锁位置

jps -l 找到死锁线程

jstack 22312(死锁线程号)找到死锁代码

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值