java多线程学习之【线程创建使用】

目前卷文化盛行,为了增强面试能力,开始了无边无际的学习,无边界不是重点,重点是要深入1万米。言归正传,本文打算做一个多线程学习的系列文章,沉淀自我。


前言

本文主要是讲解创建线程的方式,包括基本的创建,和使用不同的线程池进行创建,基本用法,使用场景,底层代码原理剖析等。

一、概念

线程池提供了一种限制和管理资源(包括执行一个任务)。每个线程池还维护一些基本统计信息,例如已完成任务的数量。

  • 使用线程池的好处:

降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

  • 不使用线程池的场景:

在流量波动大,如果不能动态的调整核心线程数,最大线程数,队列数,在资源允许的情况下,可以直接创建线程,不采用线程池也可以的。

二、创建多线程方式

2.1 继承Thread

继承Thread类,重写run方法。

package com.valley.juc.executor;

/**
 * @author valley
 * @date 2022/7/1
 * @Description TODO
 */
public class ThreadSample extends Thread{

    @Override
    public void run() {
        System.out.println("hello world!");
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("---start---");
        ThreadSample ts =new ThreadSample();
        ts.start();
        ts.join();
        System.out.println("---end---");
    }
}

2.2 实现Runnable接口

注意,2.1继承Thread类,本质上就是实现Runnable,因为Thread实现了Runnable。

package com.valley.juc.executor;

import java.util.concurrent.Callable;
import java.util.concurrent.Executors;

/**
 * @author valley
 * @date 2022/7/1
 * @Description TODO
 */
public class RunnableSample implements Runnable{

    @Override
    public void run() {
        System.out.println("hello world!");
    }

    public static void main(String[] args) throws Exception {
//        Thread thread=new Thread(new RunnableSample());
//        thread.start();

//        Callable<Object> call=Executors.callable(new RunnableSample());
//        System.out.println(call.call());

        Callable<Object> call=Executors.callable(new RunnableSample(),"ok");
        System.out.println(call.call());


    }
}

2.3 实现Callable接口

Callable和Runnable的区别

  • 具体任务的方法,一个是run方法,一个call方法,call方法有返回值,注定了Callable可以获取执行人后的结果;
  • 调用执行的方法,一个是start方法,一个是FutureTask类的run方法。注意start方法是不阻塞的,而run方法是阻塞的。当然,可以将FutureTask传给Thread,毕竟FutureTask实现了RunnableFuture接口,RunnableFuture接口继承了Runnable接口。new Thread(task2).start(),这样做的话,那两者的执行方法都是start方法,但是task2.get()会阻塞等待。
  • Executors工具类可以实现两者功能转化。
package com.valley.juc.executor;

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

/**
 * @author valley
 * @date 2022/7/4
 * @Description TODO
 */
public class CallableSample implements Callable {

    private String i;
    public CallableSample(String s) {
        this.i=s;
    }

    @Override
    public Object call() throws Exception {
        System.out.println(i+" : hello world");
        Thread.sleep(6000);
        return "123";
    }

    public static void main(String[] args) throws Exception {
        FutureTask task=new FutureTask(new CallableSample("1"));
        FutureTask task2=new FutureTask(new CallableSample("2"));
        task.run();

        System.out.println("end 1");
        String obj=(String)task.get();
        System.out.println(obj);

        new Thread(task2).start();
        System.out.println("end 2");

        String obj2=(String)task2.get();
        System.out.println(obj2);

//        Callable<Object> call= Executors.privilegedCallable(new CallableSample("1"));
//        System.out.println(call.call());
    }
}

}

三、利用工具类创建线程池

3.1 Executors工具类

3.1.1 FixedThreadPool

创建一个固定大小的线程池

package com.valley.juc.executor.threadpool;

import lombok.SneakyThrows;

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

/**
 * @author valley
 * @date 2022/7/4
 * @Description 创建一个固定大小的线程池
 */
public class NewFixedThreadPoolDemo {
        public static void main(String[] args) {
            //1.创建一个大小为5的线程池
            ExecutorService threadPool= Executors.newFixedThreadPool(5);
            //2.使用线程池执行任务一
            for (int i=0;i<5;i++){
                //给线程池添加任务
                threadPool.submit(new Runnable() {
                    @SneakyThrows
                    @Override
                    public void run() {
                        Thread.sleep(1000);
                        System.out.println("线程名"+Thread.currentThread().getName()+"在执行任务1");
                    }
                });
            }
            //2.使用线程池执行任务二
            for (int i=0;i<8;i++){
                //给线程池添加任务
                threadPool.submit(new Runnable() {
                    @SneakyThrows
                    @Override
                    public void run() {
                        Thread.sleep(1000);
                        System.out.println("线程名"+Thread.currentThread().getName()+"在执行任务2");
                    }
                });
            }

        }

}

3.1.2 SingleThreadExecutor

单个线程的线程池,充分利用线程池的优点

package com.valley.juc.executor.threadpool;

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

/**
 * @author valley
 * @date 2022/7/4
 * @Description
 * 线程池的优点:
 * 1.复用线程:不必频繁创建销毁线程
 * 2.也提供了任务队列和拒绝策略
 */
public class NewSingleThreadExecutor {
        public static void main(String[] args) {
            ExecutorService service= Executors.newSingleThreadExecutor();
            for (int i=0;i<5;i++){
                int finalI = i;
                service.submit(()->{
                    System.out.println(finalI +"线程名"+Thread.currentThread().getName());//CPU只创建了1个线程,名称始终一样
                });
            }
        }

}

3.1.3 CachedThreadPool

带缓存的线程池,适用于短时间有大量任务的场景,但有可能会占用更多的资源;线程数量随任务量而定。

package com.valley.juc.executor.threadpool;

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

/**
 * @author valley
 * @date 2022/7/4
 * @Description 带缓存的线程池,适用于短时间有大量任务的场景,但有可能会占用更多的资源;线程数量随任务量而定。
 */
public class NewCachedThreadPoolDemo {
        public static void main(String[] args) {
            //创建线程池
            ExecutorService service= Executors.newCachedThreadPool();
            //有50个任务
            for(int i=0;i<50;i++){
                int finalI = i;
                service.submit(()->{
                    System.out.println(finalI +"线程名"+Thread.currentThread().getName());//线程名有多少个,CPU就创建了多少个线程
                });
            }
        }

}

3.1.4 ScheduledThreadPool

创建执行定时任务的线程池

package com.valley.juc.executor.threadpool;

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

/**
 * @author valley
 * @date 2022/7/4
 * @Description 创建执行定时任务的线程池
 */
public class NewScheduledThreadPoolDemo {


        public static void main(String[] args) {
            ScheduledExecutorService service = Executors.newScheduledThreadPool(5);//5个线程
            System.out.println("添加任务:" + LocalDateTime.now());
            once(service);
            many(service);
        }

    private static void many(ScheduledExecutorService service) {

        //间隔时间2s>执行时间0.1s;以间隔时间为准
        //执行时间3s>间隔时间2s;以执行时间为准
//        service.scheduleAtFixedRate(new Runnable() {
//            @Override
//            public void run() {
//                System.out.println("执行任务:"+ LocalDateTime.now());
//            }
//        },3, 5, TimeUnit.SECONDS);//推迟3秒执行


        //2.推迟3秒执行;上一次任务结束2s后,下一个任务开始执行
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:"+ LocalDateTime.now()+"线程名"+Thread.currentThread().getName());
                try {
                    Thread.sleep(1000*2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },3,2,TimeUnit.SECONDS);//间隔时间成了4s;因为线程休眠了2s

    }

    /**
         * 执行一次的定时任务
         */
        public static void once(ScheduledExecutorService service) {
            service.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务:"+ LocalDateTime.now());
                }
            },3, TimeUnit.SECONDS);//推迟3秒执行
        }


}

3.1.5 NewSingleThreadScheduledExecutor

创建执行定时任务的单个线程的线程池

package com.valley.juc.executor.threadpool;

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

/**
 * @author valley
 * @date 2022/7/4
 * @Description newSingleThreadScheduledExecutor
 * 创建执行定时任务的单个线程的线程池
 */
public class NewSingleThreadScheduledExecutorDemo {
        public static void main(String[] args) {
            ScheduledExecutorService service= Executors.newSingleThreadScheduledExecutor();
            System.out.println("添加任务:"+ LocalDateTime.now());
            service.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务:"+LocalDateTime.now());
                }
            },3, TimeUnit.SECONDS);//推迟3秒执行任务

            service.scheduleAtFixedRate(new Runnable() {

                @Override
                public void run() {
                    System.out.println("执行任务2:"+LocalDateTime.now());
                }
            },3, 3,TimeUnit.SECONDS);
        }

}

3.1.6 NewWorkStealingPool

根据自身电脑配置决定创建的线程数目

package com.valley.juc.executor.threadpool;

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

/**
 * @author valley
 * @date 2022/7/4
 * @Description 根据自身电脑配置决定创建的线程数目
 */
public class NewWorkStealingPool {

        public static void main(String[] args) {
            ExecutorService service= Executors.newWorkStealingPool();
            for(int i=0;i<50;i++){
                int finalI = i;
                service.submit(()->{
                    System.out.println(finalI +"线程名"+Thread.currentThread().getName());//线程名有多少个,CPU就创建了多少个线程
                });
            }
            //创建的为守护线程,JVM不会等待守护线程结束
            while (!service.isTerminated()){

            }
        }

}

3.2 Spring包下的ThreadPoolTaskExecutor工具类

这个类则是spring包下的,是sring为我们提供的线程池类

package com.valley.juc.executor.threadpool;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author valley
 * @date 2022/7/4
 * @Description 这个类则是spring包下的,是sring为我们提供的线程池类
 */
public class ThreadPoolTaskExecutorDemo {
    public static void main(String[] args) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        int i = Runtime.getRuntime().availableProcessors();//获取到服务器的cpu内核
        executor.setCorePoolSize(5);//核心池大小
        executor.setMaxPoolSize(100);//最大线程数
        executor.setQueueCapacity(1000);//队列程度
        executor.setKeepAliveSeconds(1000);//线程空闲时间
        executor.setThreadNamePrefix("task-asyn");//线程前缀名称
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//配置拒绝策略
        for (int m=0;m<7;m++) {
            int finalI = m;
            executor.submit(() -> {
                try {
                    Thread.sleep(finalI * 100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "任务名" + finalI);
            });
        }
    }
}

3.3 CompletionService

在执行大量相互独立和同构的任务时,可以使用CompletionService

ExecutorCompletionService实现了CompletionService接口,从源码解析看,ExecutorCompletionService中内置了一个Future的BlockingQueue,在任务调用完成后,会将submit返回的future放入到BlockingQueue。用户可以通过completionQueue.take()得到future然后调用future.get()来获取任务执行结果。

package com.valley.juc.executor.threadpool;

import org.junit.Test;

import java.util.concurrent.*;

/**
 * @author valley
 * @date 2022/7/6
 * @Description TODO
 */
public class CompletionServiceDemo {

    @Test
    public void method() throws InterruptedException, ExecutionException {
        BlockingQueue q = new LinkedBlockingQueue();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(4,6,3, TimeUnit.SECONDS,q);
        CompletionService<Integer> cs = new ExecutorCompletionService<>(executor);

        cs.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                Thread.sleep(2000);
                return 1;
            }
        });
        cs.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                Thread.sleep(2000);
                return 1;
            }
        });


        for (int i = 0;i < 3;i++){
            Integer f = cs.take().get();
            System.out.println(f);
        }
        System.out.println("end");

    }
}

3.4 CompletableFuture

  • CompletableFuture创建线程有2种方式:supplyAsync(有返回值)和:runAsync(无返回值)。

  • CompletableFuture,默认依靠fork/join框架启动新的线程实现异步与并发的。

  • 它提供了函数式编程的能力,可以通过回调函数的方式处理返回结果,并且提供了转换和组合CompletableFuture的方法。

package com.valley.juc.executor.threadpool.completablefuture;

import org.junit.Test;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * @author valley
 * @date 2022/7/6
 * @Description A任务B任务完成后,才执行C任务返回值的处理
 */
public class CompletableFutureDemo {

    @Test
    public void completableFuture1(){
        System.out.println(Thread.currentThread().getName());
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("task1");
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future1 finished!"+Thread.currentThread().getName());
            return "future1 finished!";
        });

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("task2");
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future2 finished!"+Thread.currentThread().getName());
            return "future2 finished!";
        });

        CompletableFuture<Void> future3 = CompletableFuture.allOf(future1, future2);
        try {
            future3.get();
            System.out.println("task3"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("future1: " + future1.isDone() + " future2: " + future2.isDone());

    }
}

四、手动创建线程池

4.1 ThreadPoolExecutor

  • 在上面不管是ThreadPoolTaskExecutor还是Executors工具类,其实底层都是使用ThreadPoolExecutor来创建线程池。我们手动创建线程池,就是自己创建ThreadPoolExecutor。

《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

package com.valley.juc.executor.threadpool;

import java.util.concurrent.*;
import java.util.stream.IntStream;

/**
 * @author valley
 * @date 2022/7/4
 * @Description ThreadPoolExecutor
 *
 * 《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,
 * 这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
 *
 * 手动创建线程池
 *
 * 7种参数分别是:
 *
 * 核心(最少)线程数
 *
 * 最大线程数
 *
 * 闲置可存活时间
 *
 * 描述(闲置可存活时间)的单位
 *
 * 任务队列
 *
 * 线程工厂
 *
 * 拒绝策略有5种:
 */
public class ThreadPoolExecutorDemo {


        public static void main(String[] args) {
            //线程工厂
            ThreadFactory factory=new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread=new Thread(r);

                    return thread;
                }
            };
            //手动创建线程池
            ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS,
                    new LinkedBlockingDeque<>(2), factory,
                    //1.提示异常,拒绝执行多余的任务
                    // new ThreadPoolExecutor.AbortPolicy()

                    //2.忽略堵塞队列中最旧的任务
                    //new ThreadPoolExecutor.DiscardOldestPolicy()

                    //3.忽略最新的任务
                    //new ThreadPoolExecutor.DiscardPolicy()

                    //4.使用调用该线程池的线程来执行任务
                    //new ThreadPoolExecutor.CallerRunsPolicy()

                    //5.A自定义拒绝策略
                    new RejectedExecutionHandler() {
                        @Override
                        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                            System.out.println("拒绝策略");
                        }
                    }
            );
            //任务
            for (int i=0;i<7;i++){
                int finalI=i;
                threadPoolExecutor.submit(()->{
                    try {
                        Thread.sleep(finalI*100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"任务名"+finalI);
                });
            }

        }

}

五、线程池的拒绝策略和处理流程

5.1 拒绝策略

  • 拒绝策略有5种:

rejectedExectutionHandler参数字段用于配置绝策略,常用拒绝策略如下
AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException
CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。

5.1 线程池处理流程

  • 线程池处理流程

查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第二步。
查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第三步。
查看线程池是否已满,即就是是否达到最大线程池数,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。

在这里插入图片描述

六、常用场景

  • 异步处理,如果不指定线程池,采用默认的线程池
  • 异步调用接口,比如采用Okhttp
  • 多线程读写
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

blackoon88

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

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

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

打赏作者

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

抵扣说明:

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

余额充值