一、基础-Java中线程创建的方式
1.1、继承Thread类创建线程
通过继承Thread类来创建并启动多线程的一般步骤如下
1、定义Thread类的子类,并重写该类的run0)方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
2、创建Thread子类的实例,也就是创建了线程对象
3、启动线程,即调用线程的start()方法
示例:
package org.example;
/**
* @Author Js
* @Description
* @Date 2024-07-30 20:35
* @Version 1.0
**/
public class MyThread extends Thread {
@Override
public void run() {
String name = Thread.currentThread().getName();
switch (name) {
case "xcl":
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
break;
case "xc2":
for (int i = 0; i < 10; i++) {
System.out.println("好好学习java" + i);
}
break;
}
}
}
创建线程对象
package org.example;
public class Main {
public static void main(String[] args) {
MyThread myThread1=new MyThread();//创建线程对象
myThread1.setName("xc1");
myThread1.start();//启动线程
MyThread myThread2=new MyThread();//创建线程对象
myThread2.setName("xc2");
myThread2.start();//启动线程
}
}
1.2、实现Runnable接口创建线程
通过实现Runnable接口创建并启动线程一般步骤如下:
1、定义Runnable接口的实现类,一样要重写run0)方法,这个run()方法和Thread中的run()方法一样是线程的执行体
2、创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
3、第二步依然三通过调田纤程对象的:ttar0方法来启动线程
package org.example;
/**
* @Author Js
* @Description
* @Date 2024-07-30 20:53
* @Version 1.0
**/
public class MyRunable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
创建线程对象
package org.example;
/**
* @Author Js
* @Description
* @Date 2024-07-30 20:54
* @Version 1.0
**/
public class TestRunable {
public static void main(String[] args) {
Runnable myRunable=new MyRunable();//1、实例化对象
Thread thread=new Thread(myRunable);//2、构建一个线程对象
thread.setName("线程1");
thread.start();//3、启动线程
Thread threadl=new Thread(myRunable);
threadl.setName("线程2");
threadl.start();
}
}
1.3、实现Calable接口创建线程
使用Callable和Future创建线程
创建并启动有返回值的线程的步骤如下:
步骤:
1、创建Callable接口的实现类,并重写call()'方法,该方法具有返回值
2、实例化实现类对象,通过实现类对象进行入参来构建一个FutureTask对象,FutureTask对象实现了Runable接口,Future也是Runable和Future接口的实现类
3、通过FutureTask对象入参构建一个线程对象,并调用start()方法来启动执行线程
4、适用FutureTask对象的get()方法来获得线程执行的结果
示例:
package org.example;
import java.util.concurrent.Callable;
/**
* @Author Js
* @Description
* @Date 2024-07-30 21:01
* @Version 1.0
**/
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int num1=11;
int num2=22;
int sum=num1+num2;
return sum;
}
}
创建线程对象
package org.example;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @Author Js
* @Description
* @Date 2024-07-30 21:03
* @Version 1.0
**/
public class CallableTest {
public static void main(String[] args) {
MyCallable m3 = new MyCallable();//实例化对象
FutureTask<Integer> futureTask = new FutureTask(m3); //Runable对象
//创建线程
Thread t1 = new Thread(futureTask);
t1.setName("线程一");
t1.start();
//new Thread(futureTask,"线程一").start();
try {
System.out.println("线程的执行结果为:" + futureTask.get());//获得线程的返回值
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
1.4、使用线程池创建线程
下面就是…
二、概念-线程池基本概念
2.1、并发和井行的主要区别
2.1.1、处理任务不同
并发(Concurrent)
并发是一个CPU处理器同时处理多个线程任务。(宏观上是同时处理多个任务,微观上其实是CPU在多个线程之间快速的交替执行。操作系统中有一个组件叫做任务调度器,它将CPU的时间片(windows下时间片最小约为15毫秒) 配给各个线程使用,在一个时间段的线程运行时,其他线程处于挂起状态,这种就称之为并发。)
并行(parallel)
并行是多个CPU处理器同时处理多个线程任务。(当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这就被称之为并行。)
2.1.2、存在不同
并发(concurrent)
并发可以在一个CPU处理器和多个CPU处理器系统中都存在。(多个CPU处理器系统其中的一个CPU也可以进行并发操作)
并行(parallel)
并行在多个CPU处理器系统存在。
2.1.3、CPU资源不同
并发(Concurrent)
并发过程中,线程之间会去抢占CPU资源,轮流使用,(其实CPU会多个各个线程公平的分配时间片和进行执行。)
并行(parallel)
并行过程中,线程间不会抢占CPU资源,(因为是多个CPU处理器,各做各的。)
2.2、什么是线程池?
首先要理解什么是线程,线程池(thread pool),是一种线程使用模式,线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,就是可管理和维护以及分配线程的"池子
2.3、为什么使用线程池?
为了减少创建和销毁线程的次数,让每个线程都可以多次的使用,可以根据系统情况调整线程的数量,防止消耗过多内存。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,使用线程池就可以优化。
通俗的说:目的就是为了让线程对象可以反复的复用,不需要每次执行任务时构建一个新的线程,等到任务处理完再销毁。
三、源码解读·线程池源码
3.1、 线程池的核心参数
先看源码
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){
}
//参数说明:
int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//线程最大空闲时间,默认针对非核心线程
TimeUnit unit,//线程最大空闲时间单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler//拒绝策略
3.2、线程池线程增长策略
问题:
1、当向一个新创建的线程池中提交任务时候,线程池何时创建新线程,何时把任务放入等待队列,何时执行拒绝策略?
2、当一个线程池达到最大线程后,没有任务执行,哪些线程会被销毁?
3、线程池中的最大线程可以设置为多少?
4、线程池中如何来复用一个线程?
任务提交流程
线程池按以下行为执行任务:
1、当线程数小于核心线程数时,创建线程。
2、当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务(阻塞)队列。
3、当线程数大于等于核心线程数,且任务队列已满,若线程数小于最大线程数,创建工作(普通)线程。
4、若线程数等于最大线程数,则执行拒绝策略
核心线程数通常设置为CPU核心数的一个合理倍数,以充分利用系统资源并提高并发能力。最大线程数则通常设置为一个合理的上眼值,避免过多的线程竞争系统资源导致性
能下降或系统崩溃。
ThreadPoolExecutor提供了四种默认的拒绝策略:
AbortPolicy(默认): 当工作队列已满并且无法再添加新任务时,抛出RejectedExecutionException异常。
CallerRunsPollcy: 当工作队列已满并且无法再添加新任务时,由提交任务的线程来执行该任务。也就是说,任务将在提交线程的上下文中执行。
DiscardOldestPolcy: 当工作队列已满并且无法再添加新任务时,丢弃队列中最早的任务(即等待时间最长的任务),然后尝试里新提交新任务。
DiscardPolicy: 当工作队列已满并且无法再添加新任务时,直接丢弃新任务,不做任何处理。
线程池创建
package org.example;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Author Js
* @Description
* @Date 2024-07-30 22:03
* @Version 1.0
**/
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
ThreadFactory threadFactory = Executors.defaultThreadFactory();
Thread thread = threadFactory.newThread(new MyRunable());
return thread;