Java多线程

Java多线程

(1)jdk自带的四种线程池

Java通过Executors提供四种线程池,分别为:

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。

简单使用

public class UseExecutors {
    public static void main(String[] args) {
        Runnable taskOne = () -> System.out.println(Thread.currentThread().getName()+":taskOne");
        // ExecutorService pools = Executors.newCachedThreadPool();
        // ExecutorService pools = Executors.newSingleThreadExecutor();
        // ExecutorService pools = Executors.newScheduledThreadPool(10);
        ExecutorService pools = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 40; i++) {
            pools.submit(taskOne);
        }
    }
}

无论是哪一个都是调用ThreadPoolExecutor 构造方法:

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

1

8

(2)参数的意义-重要

corePoolSize 指定了线程池里的线程数量,核心线程池大小
maximumPoolSize 指定了线程池里的最大线程数量
keepAliveTime 当线程池线程数量大于corePoolSize时候,多出来的空闲线程,多长时间会被销毁
unit 时间单位,TimeUnit
workQueue 任务队列,用于存放提交但是尚未被执行的任务
threadFactory 线程工厂,用于创建线程,线程工厂就是给我们new线程的
handler 所谓拒绝策略,是指将任务添加到线程池中时,线程池拒绝该任务所采取的相应策略

常见的工作队列我们有如下选择,这些都是阻塞队列,阻塞队列的意思是,当队列中没有值的时候,取值操作会阻塞,一直等队列中产生值。

  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。
  • LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。

线程池提供了四种拒绝策略:

  • AbortPolicy:直接抛出异常,默认策略;
  • CallerRunsPolicy:用调用者所在的线程来执行任务;
  • DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务;
  • DiscardPolicy:直接丢弃任务;

线程池按以下行为执行任务

image-20210902224805090

我们来看一下这四种线程池都是使用ThreadPoolExecutor进行构造的:

newCachedThreadPool

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

通过指定参数,返回ThreadPoolExecutor来实现. 参数为:

 核心线程池大小=0
 最大线程池大小为Integer.MAX_VALUE
 线程过期时间为60s
 使用SynchronousQueue作为工作队列.

1

所以线程池为0-max个线程,并且会60s过期,实现了可以缓存的线程池。

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
核心线程池大小=传入参数
最大线程池大小为传入参数
线程过期时间为0ms
LinkedBlockingQueue作为工作队列.

通过最小与最大线程数量来控制实现定长线程池.

newScheduledThreadPool

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}
核心线程池大小=传入参数
最大线程池大小为Integer.MAX_VALUE
线程过期时间为0ms
DelayedWorkQueue作为工作队列.

主要是通过DelayedWorkQueue来实现的定时线程。

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
核心线程池大小=1
最大线程池大小为1
线程过期时间为0ms
LinkedBlockingQueue作为工作队列.

综上,Java提供的4种线程池,只是预想了一些使用场景,使用参数定义的而已,我们在使用的过程中,完全可以根据业务需要,自己去定义一些其他类型的线程池来使用(如果需要的话).

(3)自定义线程池

这里是针对JDK1.8版本,使用JDK自带的线程池会出现OOM问题,中小型公司一般很难遇到,在阿里巴巴开发文档上面有明确的标识:

image-20210901173049882

上边我们已经分析了线程池的几个参数,这几个参数核心线程数、最大线程数、活跃时间和单位根据服务器本身的性能和程序的特性设定,这个是个经验值,如果我们去设置可能效果不太好,但是起码这几个只是数字我们自定义的时候可以很简单的填入。但是线程工厂、决绝策略、阻塞队列又该怎么搞呢?

  1. 拒绝策略其实很简单,ExecutorService构造时可以不传递拒绝策略,默认使用异常抛出的方式。
  2. 阻塞队列我们搞一个定长的队列就好了,ArrayBlockingQueue<>(DEFAULT_SIZE)
  3. 线程工厂的获取我们可以使用以下的方法:

第一种办法,看看原生的怎么搞一个线程工厂:

image-20210901175325885

进入看他的源码:

static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

我们可以按照他的方式自己写一个,看不懂无所谓,起码从源码中我们看见了,线程工厂就是创建线程的,这里用到了一种设计模式,叫工厂设计模式。

public class MyThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    MyThreadFactory(String name) {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
        namePrefix = name + "-" +
                poolNumber.getAndIncrement() +
                "-thread-";
    }
    
    MyThreadFactory(){
        this("default");
    }

    public Thread newThread(Runnable r) {
    	// 就是在创建线程
        Thread t = new Thread(group, r,
                namePrefix + threadNumber.getAndIncrement(),
                0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

第二种:Google guava 工具类 提供的 ThreadFactoryBuilder

需要引入jar包,这就是别人写的类:点击File ---》 project structure

image-20210901175133485

ThreadFactory guavaThreadFactory = new ThreadFactoryBuilder().setNameFormat("retryClient-pool-").build();

1

第三种:Apache commons-lang3 提供的 BasicThreadFactory

ThreadFactory basicThreadFactory = new BasicThreadFactory.Builder()
		.namingPattern("basicThreadFactory-").build();

1
2

看怎么去定义一下线程池:

public class AsyncProcessor {

    /**
     * 默认最大并发数<br>
     */
    private static final int DEFAULT_MAX_CONCURRENT = Runtime.getRuntime().availableProcessors() * 2;

    /**
     * 线程池名称格式
     */
    private static final String THREAD_POOL_NAME = "ydlclasslog-%d";

    /**
     * 线程工厂名称
     */
    private static final ThreadFactory FACTORY = new BasicThreadFactory.Builder().namingPattern(THREAD_POOL_NAME)
            .daemon(true).build();

    /**
     * 默认队列大小
     */
    private static final int DEFAULT_SIZE = 500;

    /**
     * 默认线程存活时间
     */
    private static final long DEFAULT_KEEP_ALIVE = 60L;

    /**
     * NewEntryServiceImpl.java:689
     * Executor
     */
    private static ExecutorService executor;

    /**
     * 执行队列
     */
    private static BlockingQueue<Runnable> executeQueue = new ArrayBlockingQueue<>(DEFAULT_SIZE);

    static {
        executor = new ThreadPoolExecutor(
                DEFAULT_MAX_CONCURRENT,
                DEFAULT_MAX_CONCURRENT * 4,
                DEFAULT_KEEP_ALIVE,
                TimeUnit.SECONDS,
                executeQueue,
                FACTORY);

    }

    /**
     * 此类型无法实例化
     */
    private AsyncProcessor() {
    }
    
    public static boolean executeTask(Runnable task) {
        try {
            executor.execute(task);
        } catch (RejectedExecutionException e) {
            System.out.println("Task executing was rejected.");
            return false;
        }
        return true;
    }

    /**
     * 提交任务,并可以在稍后获取其执行情况<br>
     * 当提交失败时,会抛出 {@link }
     * @param task
     * @return
     */
    public static <T> Future<T> submitTask(Callable<T> task) {

        try {
            return executor.submit(task);
        } catch (RejectedExecutionException e) {
            throw new UnsupportedOperationException("Unable to submit the task, rejected.", e);
        }
    }
}

这个要根据实际情况来决定,比如最大容忍的响应时间,任务数,以及任务的复杂度来决定。这是一个不断积累的过程,公式反而不是很有用,因为服务器的环境是复杂的,我们其实可以通过压测来进行评估。

悲观锁

  • 悲观锁
    • 总是假设最坏的情况,每次拿数据都认为别人会修改,所以每次拿数据时候都会上锁。这样别人想拿这个数据就会阻塞直到他拿到锁(共享资源每次只给一个线程使用,其他线程阻塞,用完再将资源转让给其他线程)Java中的synchronized和Reentrantlock等独占锁都是悲观锁
  • 乐观锁

    • 假设最好的情况,每次都认为被人不会修改,所以不会上锁,但是在更新时候会判断一下再次期间别人有没有更新数据,使用版本号机制和CAS算法实现,乐观锁适用于多读的情况,这样可以提高吞吐量。Java的atomic就是使用了乐观锁的CAS实现

ThreadLocal简介

  • 简介
    • threadlocal用来提供线程内部的局部变量,这种变量在多线程环境下访问时能保证各个线程的变量相对独立其他线程的变量相对于其他线程内的变量mthreadlocal实列通常来说都是private static类型的,用来关联线程和线程上下文。
  • 作用
    • 提供线程内的局部变量,不同线程之间不会相互干扰,这种变量是在线程的生命周期内起作用
总结
线程并发:多线程并发场景
传递数据:我们可以通过Thread Local在同一线程,不同组件中传递公共变量
线程隔离:每个线程的变量相互独立,不会互相影响
  • ThreadLocal和synchronized的区别
    • syncchronized同步机制采用的是事件换空间的方式,只提供一份变量,不同线程排队访问。侧重于多个线程之间访问的资源同步
    • ThreadLocal采用的是空间换时间的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而不相互干扰。侧重于多线程中每个线程之间的数据隔离。
  • 底层实现
    • 每个ThreadLocal维护一个ThreadLocalMap,map的key是threadlocal实列本身,value才是真正要存储的值Object
    • 具体过程
      • 1672281869314
posted @ 2023-01-05 10:01  Z_WINTER  阅读(46)  评论(0)    收藏  举报