百练成钢01之线程创建笔记

线程的4种创建方式

1、继承Thread类
2、实现Runnable接口
3、实现Callable接口+FutureTask
4、线程池

方式一:继承Thread类

1、 编写Thread子类

// 继承Thread类
class MyThread extends Thread{
	@Override
	public void run() {
		System.out.println("MyThread类方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
	}
}

2、使用

public static void main(String[] args) throws InterruptedException, ExecutionException {
    System.out.println("方式1:继承Thread类=============================");
    // 方式1:继承Thread,不能获取到调用结果
    // 1、创建一个继承Thread的子类
    MyThread myThread1 = new MyThread();
    myThread1.setName("myThread1");
    // 2、启动线程
    myThread1.start();
    System.out.println("main方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
}

3、输出结果

方式1:继承Thread类=============================
main方法执行了,线程名=main,线程ID=1
MyThread类方法执行了,线程名=myThread1,线程ID=22

方式二:实现Runnable接口

1、 编写Thread子类

// 实现Runnable接口
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("MyRunnable类方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
    }
}

2、使用

public static void main(String[] args) throws InterruptedException, ExecutionException {
    System.out.println("方式2:实现Runnable接口=============================");
    // 方式2:实现Runnable接口,不能获取到调用结果
    // 1、创建线程(通过Runnable接口的子类)
    Thread myThread2 = new Thread(new MyRunnable());
    myThread2.setName("myRunnable2");
    // 2、启动线程
    myThread2.start();
    System.out.println("main方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
}

3、输出结果

方式2:实现Runnable接口=============================
main方法执行了,线程名=main,线程ID=1
MyRunnable类方法执行了,线程名=myRunnable2,线程ID=23

方式三:实现Callable接口+FutureTask

1、编写Callable接口子类

// 执行正常
class MyCallable1 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("MyCallable类方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
        return 1;
    }
}
// 执行异常
class MyCallable2 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("MyCallable类方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
        int i = 1/0;
        return 1;
    }
}

2、使用

public static void main(String[] args) throws InterruptedException, ExecutionException {
    System.out.println("main方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
    // 方式3:实现 Callable接口+FutureTask,可以拿到返回结果,可以处理异常,其中Callable<T> T为返回值泛型
    // 1、线程正常执行,获取返回值
    // 1.1 创建线程(通过Callable接口的子类和FutureTask)
    FutureTask<Integer> futureTask1 = new FutureTask<>(new ThreadCreateMethodClassTest.MyCallable1());
    Thread myThread3 = new Thread(futureTask1);
    myThread3.setName("myCallable3");
    // 1.2 启动线程
    myThread3.start();
    Thread.sleep(3000);
    // 1.3 获取返回值
    System.out.println("打印执行结果:"+futureTask1.get());
    
    // 2、线程异常执行,获取异常
    // 2.1 创建线程(通过Callable接口的子类和FutureTask)
    FutureTask<Integer> futureTask2 = new FutureTask<>(new ThreadCreateMethodClassTest.MyCallable2());
    Thread myThread31 = new Thread(futureTask2);
    myThread31.setName("myCallable3");
    // 2.2 启动线程
    myThread31.start();
    // 2.3 获取异常
    System.out.println("打印执行异常信息:"+futureTask2.get());
}

3、执行结果

方式3:实现 Callable 接口 + FutureTask=============================
MyCallable类方法执行了,线程名=myCallable3,线程ID=24
打印执行结果:1
MyCallable类方法执行了,线程名=myCallable3,线程ID=25
`Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at cn.study.zgm.thread01.threadcreate.ThreadCreateMethodClassTest.main(ThreadCreateMethodClassTest.java:41)
Caused by: java.lang.ArithmeticException: / by zero
	at cn.study.zgm.thread01.threadcreate.ThreadCreateMethodClassTest$MyCallable2.call(ThreadCreateMethodClassTest.java:56)
	at cn.study.zgm.thread01.threadcreate.ThreadCreateMethodClassTest$MyCallable2.call(ThreadCreateMethodClassTest.java:52)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.lang.Thread.run(Thread.java:750)` 

方式四:使用线程池创建

ThreadPoolExecutor创建
Executors创建(也是通过ThreadPoolExecutor来进行封装创建的)

1、线程池创建

使用ThreadPoolExecutor构造器创建
在这里插入图片描述
方法参数介绍
corePoolSize:核心线程数(要保留在线程池中的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut)
corePoolSize:核心线程数(如果等于0,则任务执行完后,没有任务请求进入时销毁线程池中的线程。如果大于0,即使本地任务执行完毕,核心线程也不会被销毁。设置过大会浪费系统资源,设置过小导致线程频繁创建)
maximumPoolSize: 最大线程数(必须大于等于1,且大于等于corePoolSize。如果与corePoolSize相等,则线程池大小固定。如果大于corePoolSize,则最多创建maximumPoolSize个线程执行任务,其他任务加入到workQueue缓存队列中,当workQueue为空且执行任务数小于maximumPoolSize时,线程空闲时间超过keepAliveTime会被回收)
keepAliveTime: 线程空闲时间(线程池中线程空闲时间达到keepAliveTime值时,线程会被销毁,只到剩下corePoolSize个线程为止。默认情况下,线程池的最大线程数大于corePoolSize时,keepAliveTime才会起作用。如果allowCoreThreadTimeOut被设置为true,即使线程池的最大线程数等于corePoolSize,keepAliveTime也会起作用(回收超时的核心线程))
unit:单位(keepAliveTime单位)
workQueue: 工作队列(当请求线程数大于maximumPoolSize时,线程进入BlockingQueue阻塞队列)
threadFactory :线程工厂(用来生产一组相同任务的线程。主要用于设置生成的线程名词前缀、是否为守护线程以及优先级等。设置有意义的名称前缀有利于在进行虚拟机分析时,知道线程是由哪个线程工厂创建的)
handler:拒绝策略(执行拒绝策略对象。当达到任务缓存上限时(即超过workQueue参数能存储的任务数),执行拒绝策略,可以看做简单的限流保护)

拒绝策略介绍(RejectedExecutionHandler)
1、ThreadPoolExecutor.AbortPolicy: 【默认】丢弃任务并抛出RejectedExecutionException异常
2、ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常
3、ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中
4、ThreadPoolExecutor.CallerRunsPolicy: 由调用线程(提交任务的线程)处理该任务,调用任务的run()方法绕过线程池直接执行

创建如下

// 通过线程池构造器创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(20), threadFactory, new ThreadPoolExecutor.AbortPolicy());

2、使用

线程池使用,分为无返回值/有返回值两种

public static void main(String[] args) {
    List<Integer> numberList = new ArrayList<>(5);
    for (int i = 0; i < 5; i++) {
        numberList.add(i);
    }
    // 1、无返回值调度
    System.out.println("ThreadPoolExecutor 无返回值=====================================");
    ThreadPoolExecutor executor1 = new ThreadPoolExecutor(10, 100, 10, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
    try {
        // 无返回值:execute
        for (Integer number : numberList) {
            executor1.execute(() -> {
                System.out.println("打印参数:【"+number+"】,打印调度线程,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
            });
        }
    }finally {
        executor1.shutdown();
    }
    Thread.sleep(3000);
    // 2、有返回值调度
    System.out.println("ThreadPoolExecutor 有返回值=====================================");
    ThreadPoolExecutor executor2 = new ThreadPoolExecutor(10, 100, 10, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
    try {
        // 有返回值:submit
        LinkedBlockingDeque<Future<Integer>> blockingDeque = new LinkedBlockingDeque<>();
        for (Integer number : numberList) {
            Future<Integer> future = executor2.submit(() -> {
                System.out.println("打印参数:【"+number+"】,打印调度线程,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
                int i = 1/0;
                return number*2;
            });
            blockingDeque.add(future);
        }
        List<Integer> resultList = new ArrayList<>(5);
        while (!blockingDeque.isEmpty()){
            Integer integer = blockingDeque.take().get();
            resultList.add(integer);
        }
        System.out.println("输出返回值"+resultList.toString());
    }finally {
        executor2.shutdown();
    }
}

3、执行结果

ThreadPoolExecutor 无返回值=====================================
打印参数:【0】,打印调度线程,线程名=pool-2-thread-1,线程ID=22
打印参数:【1】,打印调度线程,线程名=pool-2-thread-2,线程ID=23
打印参数:【2】,打印调度线程,线程名=pool-2-thread-3,线程ID=24
打印参数:【4】,打印调度线程,线程名=pool-2-thread-5,线程ID=26
打印参数:【3】,打印调度线程,线程名=pool-2-thread-4,线程ID=25
ThreadPoolExecutor 有返回值=====================================
打印参数:【2】,打印调度线程,线程名=pool-3-thread-3,线程ID=29
打印参数:【0】,打印调度线程,线程名=pool-3-thread-1,线程ID=27
打印参数:【3】,打印调度线程,线程名=pool-3-thread-4,线程ID=30
打印参数:【1】,打印调度线程,线程名=pool-3-thread-2,线程ID=28
打印参数:【4】,打印调度线程,线程名=pool-3-thread-5,线程ID=31
`Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at cn.study.zgm.thread01.threadcreate.ThreadCreateMethodClassTest.main(ThreadCreateMethodClassTest.java:99)
Caused by: java.lang.ArithmeticException: / by zero
	at cn.study.zgm.thread01.threadcreate.ThreadCreateMethodClassTest.lambda$main$1(ThreadCreateMethodClassTest.java:92)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:750)`

线程池探讨

线程池执行流程

1、线程初始化
根据用户给定参数创建一个线程池,参数包括核心线程数,最大线程数,存活时间以及队列。线程池初始化时,核心线程会被创建并保持活跃状态,而非核心线程处于非活跃状态。
2、任务放入线程池
当任务被放到线程池中时,线程池会首先检查当前是否有可用的线程,如果有,则将任务分配给该线程执行,如果没有则根据线程池的设置来处理该任务,这些设置有:
2.1 如果核心线程数未达到最大值,则新建线程处理任务;
2.2 如果核心线程数达到最大值,且队列可以容纳新的任务,则将任务放进队列;
2.3 如果核心线程数达到最大值,且队列满了,且总线程数未到最大值,则新建线程处理任务;
2.4 如果核心线程数达到最大值,队列满了,且总线程数已达到最大值,则拒绝任务(触发拒绝策略);
3、任务执行
当任务被分配给线程时,线程会调用任务的run方法,将任务执行完成。
4、任务结束
当任务执行完毕时,线程会退出,线程池会根据设置的线程来确定是保留线程还是将线程回收,以复用现有的线程资源。如果线程池中没有正在运行的线程,线程池会根据之前任务的处理情况,从队列中取出下一个任务,将其分配给现有线程或新建线程来执行
略进行处理

2、线程池问题验证

1、核心线程是在处理任务时才进行创建,而不是随线程池创建而创建。

验证代码
getPoolSize获取线程池中的线程数

public static void main(String[] args) throws InterruptedException, ExecutionException {
    // 1、验证核心线程在任务创建时进行创建,而不是随线程池创建而创建
    LinkedBlockingQueue taskBlockQueue = new LinkedBlockingQueue<>(10);
    ThreadPoolExecutor executor1 = new ThreadPoolExecutor(5, 10, 10, TimeUnit.MILLISECONDS,taskBlockQueue);
    Thread.sleep(500);
    System.out.println("打印线程池中核心线程数="+executor1.getPoolSize());
    System.out.println("线程池处理任务=================================");
    try {
        LinkedBlockingDeque<Future<Long>> linkedBlockingDeque = new LinkedBlockingDeque<>();
        for (int i = 0; i < 5; i++) {
            Future<Long> submit = executor1.submit(() -> {
                Thread.sleep(1);
                return Thread.currentThread().getId();
            });
            linkedBlockingDeque.add(submit);
            System.out.println("打印线程池中核心线程数="+executor1.getPoolSize());
        }
        while (!linkedBlockingDeque.isEmpty()){
            linkedBlockingDeque.take().get();
        }
    } catch (Exception e) {
        throw e;
    } finally {
        System.out.println("线程池资源释放前=================================");
        System.out.println("打印线程池中核心线程数="+executor1.getPoolSize());
        executor1.shutdown();
        System.out.println("线程池资源释放后=================================");
    }
    Thread.sleep(1000);
    System.out.println("打印线程池中核心线程数="+executor1.getPoolSize());
}

输出结果

打印线程池中核心线程数=0
线程池处理任务=================================
打印线程池中核心线程数=1
打印线程池中核心线程数=2
打印线程池中核心线程数=3
打印线程池中核心线程数=4
打印线程池中核心线程数=5
线程池资源释放前=================================
打印线程池中核心线程数=5
线程池资源释放后=================================
打印线程池中核心线程数=0

2、当核心线程数和任务队列已满时,会创建非核心线程,此时会将新任务(还未加入阻塞队列的任务)直接分配给非核心线程处理,而不是先处理阻塞队列中的队列,再将新任务添加到阻塞队列等待执行。

代码验证
设置任务执行睡眠时间,保证任务添加直到阻塞队列满时,还未有线程,防止执行完的线程对结果进行干扰。
核心线程数5,最大线程数10,阻塞队列大小为10

public static void main(String[] args) throws InterruptedException, ExecutionException {
    ThreadFactory threadFactory = runnable -> {
        Thread thread = new Thread(runnable);
        thread.setName("[子线程]");
        return thread;
    };
    LinkedBlockingDeque blockingDeque = new LinkedBlockingDeque<>(10);
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.MILLISECONDS,blockingDeque , threadFactory, new ThreadPoolExecutor.AbortPolicy());
    try {
        Set<Long> threadIdSet = new HashSet<>();
        LinkedBlockingDeque<Future<Long>> linkedBlockingDeque = new LinkedBlockingDeque<>();
        for (int i = 0; i < 20; i++) {
            int number = i;
            Thread.sleep(5);
            Future<Long> submit = executor.submit(() -> {
                Thread.sleep(400);
                System.out.println("打印参数【" + number + "】,打印线程名=" + Thread.currentThread().getName() + ",打印线程ID=" + Thread.currentThread().getId());
                return Thread.currentThread().getId();
            });
            linkedBlockingDeque.add(submit);
            System.out.println("任务队列blockingDeque个数:"+blockingDeque.size());
        }
        while (!linkedBlockingDeque.isEmpty()){
            Long aLong = linkedBlockingDeque.take().get();
            threadIdSet.add(aLong);
        }
        System.out.println("线程ID个数:"+threadIdSet.size());
        System.out.println(threadIdSet.toString());
    } catch (Exception e) {
        throw e;
    } finally {
        executor.shutdown();
    }
}

输出结果
当任务数为20,结果如下:

任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:1
任务队列blockingDeque个数:2
任务队列blockingDeque个数:3
任务队列blockingDeque个数:4
任务队列blockingDeque个数:5
任务队列blockingDeque个数:6
任务队列blockingDeque个数:7
任务队列blockingDeque个数:8
任务队列blockingDeque个数:9
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
打印参数【0】,打印线程名=[子线程],打印线程ID=22
打印参数【1】,打印线程名=[子线程],打印线程ID=23
打印参数【2】,打印线程名=[子线程],打印线程ID=24
打印参数【3】,打印线程名=[子线程],打印线程ID=25
打印参数【4】,打印线程名=[子线程],打印线程ID=26
打印参数【15】,打印线程名=[子线程],打印线程ID=27
打印参数【16】,打印线程名=[子线程],打印线程ID=28
打印参数【17】,打印线程名=[子线程],打印线程ID=29
打印参数【18】,打印线程名=[子线程],打印线程ID=30
打印参数【19】,打印线程名=[子线程],打印线程ID=31
打印参数【5】,打印线程名=[子线程],打印线程ID=22
打印参数【7】,打印线程名=[子线程],打印线程ID=24
打印参数【6】,打印线程名=[子线程],打印线程ID=23
打印参数【8】,打印线程名=[子线程],打印线程ID=25
打印参数【9】,打印线程名=[子线程],打印线程ID=26
打印参数【10】,打印线程名=[子线程],打印线程ID=27
打印参数【11】,打印线程名=[子线程],打印线程ID=28
打印参数【12】,打印线程名=[子线程],打印线程ID=29
打印参数【13】,打印线程名=[子线程],打印线程ID=30
打印参数【14】,打印线程名=[子线程],打印线程ID=31
线程ID个数:10
[22, 23, 24, 25, 26, 27, 28, 29, 30, 31]

当任务数大于20(此处验证任务数为21),(触发拒绝策略)结果如下:

任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:1
任务队列blockingDeque个数:2
任务队列blockingDeque个数:3
任务队列blockingDeque个数:4
任务队列blockingDeque个数:5
任务队列blockingDeque个数:6
任务队列blockingDeque个数:7
任务队列blockingDeque个数:8
任务队列blockingDeque个数:9
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
`Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@47f6473 rejected from java.util.concurrent.ThreadPoolExecutor@6615435c[Running, pool size = 10, active threads = 10, queued tasks = 10, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
	at cn.study.zgm.thread01.threadcreate.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:56)`
打印参数【0】,打印线程名=[子线程],打印线程ID=22
打印参数【1】,打印线程名=[子线程],打印线程ID=23
打印参数【2】,打印线程名=[子线程],打印线程ID=24
打印参数【3】,打印线程名=[子线程],打印线程ID=25
打印参数【4】,打印线程名=[子线程],打印线程ID=26
打印参数【15】,打印线程名=[子线程],打印线程ID=27
打印参数【16】,打印线程名=[子线程],打印线程ID=28
打印参数【17】,打印线程名=[子线程],打印线程ID=29
打印参数【18】,打印线程名=[子线程],打印线程ID=30
打印参数【19】,打印线程名=[子线程],打印线程ID=31
打印参数【5】,打印线程名=[子线程],打印线程ID=22
打印参数【6】,打印线程名=[子线程],打印线程ID=23
打印参数【7】,打印线程名=[子线程],打印线程ID=24
打印参数【8】,打印线程名=[子线程],打印线程ID=25
打印参数【9】,打印线程名=[子线程],打印线程ID=26
打印参数【11】,打印线程名=[子线程],打印线程ID=28
打印参数【10】,打印线程名=[子线程],打印线程ID=27
打印参数【12】,打印线程名=[子线程],打印线程ID=29
打印参数【14】,打印线程名=[子线程],打印线程ID=31
打印参数【13】,打印线程名=[子线程],打印线程ID=30
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值