Java进阶(5)——创建多线程的方法extends Thread和implements Runnable的对比 & 线程池及常用的线程池(2)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

public class Demo1 {
public static class MyThread extends Thread{
@Override
public void run() {
System.out.println(“通过继承Thread创建多线程”);
}
}

public static void main(String[] args) {
    MyThread myThread = new MyThread();
    myThread.start();
}

}


注意要调用`start()`方法后,该线程才算启动!



> 
> 我们在程序里面调用了start()方法后,虚拟机会先为我们创建一个线程,然后等到这个线程第一次得到时间片时再调用run()方法。
> 
> 
> 注意不可多次调用start()方法。在第一次调用start()方法后,再次调用start()方法会抛出IllegalThreadStateException异常。
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/26ca4904fc7c404eb668a112f0caaf95.png)


### Runnable接口创建多线程


![在这里插入图片描述](https://img-blog.csdnimg.cn/e3d40a5672084a37bb06161463282862.png)



@FunctionalInterface
public interface Runnable {
public abstract void run();
}


![在这里插入图片描述](https://img-blog.csdnimg.cn/f637cbbd5ae64349b8d76cb24ce4017e.png)



package com.tianju.book.jpa.syn;

public class Demo2 {
public static class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println("继承runnable实现多线程");
    }
}

public static void main(String[] args) {
    Thread thread = new Thread(() -> {
        System.out.println("匿名内部类");
        new MyThread().run();
    });
    thread.start();
}

}


### Thread类的常用方法


这里介绍一下Thread类的几个常用的方法:


* currentThread():静态方法,返回对当前正在执行的线程对象的引用;
* start():开始执行线程的方法,java虚拟机会调用线程内的run()方法;
* yield():yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。这里需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的;
* sleep():静态方法,使当前线程睡眠一段时间;
* join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的;


### 两者的对比


实现一个自定义的线程类,可以有继承`Thread`类或者实现`Runnable`接口这两种方式,它们之间有什么优劣呢?


* 由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
* Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
* Runnable接口出现,降低了线程对象和线程任务的耦合性。
* 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。


所以,我们通常优先使用“实现`Runnable`接口”这种方式来自定义线程类。


## 线程池


### 是啥?为啥用?


线程池是一种用于管理和复用线程的机制。它是一种线程管理的抽象概念,可以有效地管理线程的创建、执行和销毁,以提高应用程序的性能和资源利用率。


线程池中包含一组预先创建的线程,这些线程可以被重复使用来执行任务。当有任务需要执行时,线程池中的线程会被分配给任务,并在任务完成后返回线程池,以便可以被其他任务复用。这样可以避免频繁地创建和销毁线程,减少了线程创建和销毁的开销,提高了系统的响应速度和资源利用率。


使用线程池主要有以下三个原因:


1. 创建/销毁线程需要消耗系统资源,线程池可以**复用已创建的线程**。
2. **控制并发的数量**。并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃。(主要原因)
3. **可以对线程做统一管理**。


### 结合案例理解常用的线程池


`Executors`类中提供的几个静态方法来创建线程池


### 继承Runnable接口的工人实体类



> 
> name:工人名字,
> 
> 
> workTime:工作时间,用java的毫秒模拟现实中的1分钟
> 
> 
> materials:物料的数量,假设一共有100个物料;
> 
> 
> 



package com.tianju.book.jpa.syn;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* 工厂仿真的工人
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Worker implements Runnable{
private String name;

private Integer workTime;

private static Integer materials=100; // 物料数量

@Override
public void run() {
    System.out.println(name+"开始工作");
    try {
        Thread.sleep(workTime); // 模拟工作时间
        materials--; // 每次消耗1个物料
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println(name+"工作完成,耗时"+workTime+"分钟,"+"当前剩余物料:"+materials);
}

}


### newSingleThreadExecutor(单线程)



> 
> 创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
> 
> 
> 



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


有且仅有一个核心线程( corePoolSize == maximumPoolSize=1),使用了LinkedBlockingQueue(容量很大),所以,**不会创建非核心线程**。所有任务按照**先来先执行**的顺序执行。如果这个唯一的线程不空闲,那么新来的任务会存储在任务队列里等待执行。



package com.tianju.book.jpa.syn;

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

/**
* 进行工厂的仿真
*/
public class FactorySimulationSingle {

public static void main(String[] args) {
    // 工人池,单线程的线程池
    ExecutorService workerPool = Executors.newSingleThreadExecutor();
    for (int i= 1;i<10;i++){
        Worker worker = new Worker("worker" + i, i \* 10);
        workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
    }
    Thread thread2 = new Thread(() -> {
        for (int i = 1; i < 10; i++) {
            Worker worker = new Worker("TH-worker" + i, i \* 10);
            workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
        }
    });
    thread2.start();

    try {
        Thread.sleep(20000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    workerPool.shutdown();
}

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/c6a47222741849d6838bfdce94b190d8.png)


### newFixedThreadPool(加锁)



> 
> 创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
> 
> 
> 



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


核心线程数量和总线程数量相等,都是传入的参数nThreads,所以只能创建核心线程,不能创建非核心线程。因为LinkedBlockingQueue的默认大小是Integer.MAX\_VALUE,故如果核心线程空闲,则交给核心线程处理;如果核心线程不空闲,则入列等待,直到核心线程空闲。


**与CachedThreadPool的区别**:


* 因为 corePoolSize == maximumPoolSize ,所以FixedThreadPool只会创建核心线程。 而CachedThreadPool因为corePoolSize=0,所以只会创建非核心线程。
* 在 getTask() 方法,如果队列里没有任务可取,线程会一直阻塞在 LinkedBlockingQueue.take() ,线程不会被回收。 CachedThreadPool会在60s后收回。
* 由于线程不会被回收,会一直卡在阻塞,所以**没有任务的情况下, FixedThreadPool占用资源更多**。
* 都几乎不会触发拒绝策略,但是原理不同。FixedThreadPool是因为阻塞队列可以很大(最大为Integer最大值),故几乎不会触发拒绝策略;CachedThreadPool是因为线程池很大(最大为Integer最大值),几乎不会导致线程数量大于最大线程数,故几乎不会触发拒绝策略。



> 
> ExecutorService workerPool = Executors.newFixedThreadPool(1);
> 
> 
> 设置线程池中线程数量为1,此时就等同于newSingleThreadExecutor
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/5eb550960cb74fed8ee701a567ec9c54.png)



package com.tianju.book.jpa.syn;

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

/**
* 进行工厂的仿真
*/
public class FactorySimulation {

public static void main(String[] args) {
    // 工人池,固定大小为5
    ExecutorService workerPool = Executors.newFixedThreadPool(5);

    for (int i= 1;i<10;i++){
        Worker worker = new Worker("worker" + i, 1);
        workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
    }

    Thread thread1 = new Thread(() -> {
        for (int i = 1; i < 10; i++) {
            Worker worker = new Worker("TH1-worker" + i, i \* 10);
            workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
        }
    });

    Thread thread2 = new Thread(() -> {
        for (int i = 1; i < 10; i++) {
            Worker worker = new Worker("TH2-worker" + i, i \* 10);
            workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
        }
    });

    thread1.start();
    thread2.start();

    try {
        Thread.sleep(20000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    workerPool.shutdown();
}

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/d9792fc9f2e740c19edd1a7ce0f4b038.png)


### newCachedThreadPool



> 
> 创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说VM)能够创建的最大线程大小。
> 
> 
> 



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


`CacheThreadPool`的**运行流程**如下:


1. 提交任务进线程池。
2. 因为**corePoolSize**为0的关系,不创建核心线程,线程池最大为Integer.MAX\_VALUE。
3. 尝试将任务添加到**SynchronousQueue**队列。
4. 如果SynchronousQueue入列成功,等待被当前运行的线程空闲后拉取执行。如果当前没有空闲线程,那么就创建一个非核心线程,然后从SynchronousQueue拉取任务并在当前线程执行。
5. 如果SynchronousQueue已有任务在等待,入列操作将会阻塞。


当需要执行很多**短时间**的任务时,CacheThreadPool的线程复用率比较高, 会显著的**提高性能**。而且线程60s后会回收,意味着即使没有任务进来,CacheThreadPool并不会占用很多资源。



package com.tianju.book.jpa.syn;

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

/**
* 进行工厂的仿真
*/
public class FactorySimulationCached {

public static void main(String[] args) {
    // 工人池,固定大小为5 
    ExecutorService workerPool = Executors.newCachedThreadPool();
    Thread thread1 = new Thread(() -> {
        for (int i = 1; i < 10; i++) {
            Worker worker = new Worker("TH1\_worker" + i, i \* 10);
            workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
        }
    });

    Thread thread2 = new Thread(() -> {
        for (int i = 1; i < 10; i++) {
            Worker worker = new Worker("TH2\_worker" + i, i \* 10);
            workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
        }
    });

    thread1.start();
    thread2.start();

    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    workerPool.shutdown();
}

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/a55c637474df43ed9d4e27f94167e98c.png)


### newScheduledThreadPool(定时任务)



> 
> 创建一个定长线程池,支持定时及周期性任务执行。
> 
> 
> 



public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}

//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}




![img](https://img-blog.csdnimg.cn/img_convert/e9adbf1d97793c7a44f327b28d3e3aa6.png)
![img](https://img-blog.csdnimg.cn/img_convert/0bfdbc67062fa6595af7e36c1a28e6e9.png)
![img](https://img-blog.csdnimg.cn/img_convert/36217eb45e43824a9ed7e092905fd7d3.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618631832)**

corePoolSize, Integer.MAX\_VALUE,
          DEFAULT\_KEEPALIVE\_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

[外链图片转存中…(img-1RAXrMQT-1715791816434)]
[外链图片转存中…(img-p4FZU5sJ-1715791816435)]
[外链图片转存中…(img-lz7W2K5F-1715791816437)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值