进程是:一个正在运行应用程序如火绒
线程是:一个进程中的执行单元 如火绒的垃圾清理
不同的进程是独立的:不共享资源。
不同的线程:堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈。
存在的意义就是提高工作效率同时也会伴随线程安全问题。即对共享的数据操作
但单核的电脑实际意义上不能做到线程并行,只能近似于线程并行。
hread类位于java.lang包,JDK1.0引入。线程的创建、调度、执行、销毁等由内核进行控制,调度过程通过抢占式策略进行调度。
方法 | 描述 |
---|---|
Thread() | 创建一个默认设置的线程对象实例 |
Thread(Runnable target) | 创建一个包含可执行对象的线程实例 |
Thread(Runnable target, String name) | 创建一个包含可执行对象,指定名称的线程对象 |
Thread(String name) | 创建一个指定名称的线程对象 |
Thread(ThreadGroup group, Runnable target) | 创建一个指定线程组,包含可执行对象的线程对象实例 |
Thread(ThreadGroup group, Runnable target, String name) | 创建一个指定线程组,包含可执行对象,指定线程名称的线程对象实例 |
Thread(ThreadGroup group, Runnable target, String name, long stackSize) | 创建一个指定线程组、包含可执行对象、指定名称以及堆栈大小的线程对象实例 |
Thread(ThreadGroup group, String name) | 创建一个指定线程组,线程名称的线程实例 |
线程组的解释:可以包含多个线程,且每个线程可访问本线程组的相关信息
切换为中途加入的线程,
有参数时:参数为阻塞的时间
无参数时:直到线程阻塞完成。
使用场景:当一个线程需要另一个线程的结果。
join() 方法被interrupt()会抛出异常,join()方法使用后会释放锁,sleep(long)方法却不释放锁;
// 设置守护线程 (备胎 当主线程执行之后自动结束)
new Thread().setDaemon(true);
// 让出执行权 原线程还可以继续抢
Thread.yield();
Thread thread = new Thread();
// 插入线程 插入到其它线程 且join线程执行完之后 继续执行下面的
thread.join();
注意:优先级只是抢占锁时的概率大。并非优先级高的一定比优先级低的先执行
设置守护线程之后,主线程结束了,守护线程也随之结束,守护线程并不一定会执行完
1.继承Thread类
2.重写run方法
3.得到对象
4.调用其start方法
public class MyThread extends Thread{
// 重写run方法
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("name:"+Thread.currentThread().getName()+" i="+i);
}
}
}
/**
* @description: 集成Thread
* @author: lx
* @date: 2023/3/31 13:53
* @param: []
* @return: void
**/
@Test
public void test2(){
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.start();
thread2.start();
thread3.start();
}
1.实现Runnable接口
2.重写run方法
3.得到Thread对象把 Runnable当参数传进去
4.调用其start方法
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("name:"+Thread.currentThread().getName()+" i="+i);
}
}
}
/**
* @description: 实现Runnable接口
* @author: lx
* @date: 2023/3/31 14:01
* @param: []
* @return: void
**/
@Test
public void test3(){
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
thread1.start();
thread2.start();
}
1.实现Callable接口
2.重写fun方法
3.得到FutureTask对象
4.得到Thread对象
5.执行start方法
/**
* @Author: lx
* @CreateTime: 2023-03-31 14:00
* @Description: TODO
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("name:"+Thread.currentThread().getName()+" i="+i);
}
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// myCallable 多线程要执行任务的对象
MyCallable myCallable = new MyCallable();
// FutureTask 管理多线程的结果
FutureTask<Integer> f = new FutureTask<Integer>(myCallable);
FutureTask<Integer> f1 = new FutureTask<Integer>(myCallable);
// 创建线程的对象
Thread thread = new Thread(f);
// 启动线程
thread.start();
// 创建线程的对象
Thread thread1 = new Thread(f1);
// 启动线程
thread1.start();
// 获取多线程运行的结果
System.out.println(f.get());
}
主要是继承只能继承一个类,但接口可以多实现。
callable的优势:有返回值。且可以处理异常
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象;
1:线程和任务分离,提升线程重用性;
2:控制线程并发数量,降低服务器压力,统一管理所有线程;
3:提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间;
package top.remained.juc.base;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author: lx
* @CreateTime: 2023-03-31 16:49
* @Description: TODO
*/
public class BatchAddDemo {
private static final int THREAD_POOL_SIZE = 10;
public static void main(String[] args) throws InterruptedException {
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
// 批量增加任务
for (int i = 0; i < 100; i++) {
executorService.execute(new Task(i));
// Thread.sleep(1);
}
// 关闭线程池
executorService.shutdown();
}
private static class Task implements Runnable {
private final int id;
public Task(int id) {
this.id = id;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" Task " + id + " is running");
}
}
}
在上述代码中,我们使用Executors.newFixedThreadPool()方法创建一个大小为10的固定线程池。然后使用execute()方法提交100个任务,每个任务都是一个实现了Runnable接口的Task对象。
最后,我们调用shutdown()方法关闭线程池。这将使线程池停止接受新任务,并等待所有已提交的任务完成。如果不调用shutdown()方法,线程池将一直运行直到应用程序退出。
ThreadPoolExecutor继承自AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//核心线程数
5,//最大线程数 设计
100,//最大存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3),//任务队列 设计:核心线程数/单个任务执行时间 *2
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy() //拒绝策略
);
线程池核心线程大小
线程池最大线程数量
空闲线程存活时间
空闲线程存活时间单位
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:
①ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,等待被调度,有界的数组可以防止资源耗尽问题(不会一直接收任务)。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
②LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
③SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
④PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:
①CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
②AbortPolicy
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
③DiscardPolicy
该策略下,直接丢弃任务,什么都不做。
④DiscardOldestPolicy
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
线程池的工具类 通过调用不同的方法返回不同的返回类型
使用工厂类获取线程池对象
Executors类的底层实现便是ThreadPoolExecutor
里面的线程可重用 且在第一次使用时才创建 数量不做限制,以任务优先,任务多的话,耗性能
适用场景:
需要快速处理突发性强、耗时较短的任务场景,如Netty的NIO处理场景、REST API接口的瞬时削峰场景
@Test
public void test1(){
// 1.使用工厂类获取线程池对象
ExecutorService pool = Executors.newCachedThreadPool();
// 2.提交任务
for (int i = 0; i <10 ; i++) {
pool.submit(new Runnable1(i));
}
}
@Test
public void test2(){
// 1.使用工厂类获取线程池对象
ExecutorService pool = Executors.newCachedThreadPool(new ThreadFactory() {
int n = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"线程名称"+n++);
}
});
// 2.提交任务
for (int i = 0; i <10 ; i++) {
pool.submit(new Runnable1(i));
}
}
创建可重用固定线程数的线程池
特点
如果线程数没有达到“固定数量”,每次提交一个任务线程池内就创建一个新线程,直到线程达到线程池固定的数量
线程池的大小一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
在接收异步任务的执行目标实例时,如果池中的所有线程均在繁忙状态,新任务会进入阻塞队列中(无界的阻塞队列)
适用场景:
需要任务长期执行的场景
CPU密集型任务
@Test
public void test3(){
// 1.使用工厂类获取线程池对象
ExecutorService pool = Executors.newFixedThreadPool(4);// 参数 线程数量
// 2.提交任务
for (int i = 0; i <10 ; i++) {
pool.submit(new Runnable1(i));
}
}
@Test
public void test4(){
// 1.使用工厂类获取线程池对象
ExecutorService pool = Executors.newFixedThreadPool(3, new ThreadFactory() {
int n = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"线程名称"+n++);
}
});// 参数 线程数量
// 2.提交任务
for (int i = 0; i <10 ; i++) {
pool.submit(new Runnable1(i));
}
}
单线程 无界 重点在于安全
适用场景:任务按照提交次序,一个任务一个任务地逐个执行的场景
@Test
public void test5(){
// 1.使用工厂类获取线程池对象
ExecutorService pool = Executors.newSingleThreadExecutor();// 单个无界线程
// 2.提交任务
for (int i = 0; i <10 ; i++) {
pool.submit(new Runnable1(i));
}
// 3.关闭线程池 不接受新的任务 原来的任务仍在执行
pool.shutdown();
// pool.shutdownNow(); 如有缓存的任务,返回这些任务并停止 运行
}
@Test
public void test6(){
// 1.使用工厂类获取线程池对象
ExecutorService pool = Executors.newSingleThreadExecutor(new ThreadFactory() {
int n = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"线程名称"+n++);
}
});// 单个无界线程
// 2.提交任务
for (int i = 0; i <10 ; i++) {
pool.submit(new Runnable1(i));
}
}
}
可重复 可延迟 可定期重复
1.延迟不循环
package top.remained.juc.pool.executorservice;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @Author: lx
* @CreateTime: 2023-04-04 18:26
* @Description: TODO 有线程池的特性,也可以实现任务循环执行,可以看作是一个简单地定时任务组件,
* 因为有线程池特性,所以任务之间可以多线程并发执行,互不影响,当任务来的时候,才会真正创建线程去执行
* schedule()可以对任务进行延迟处理
*/
public class ScheduledExecutorServiceTest {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
System.out.println("开启的时间:"+new Date(System.currentTimeMillis()));
pool.schedule(new Runnable3(1),5, TimeUnit.SECONDS);
pool.schedule(new Runnable3(2),5, TimeUnit.SECONDS);
pool.shutdown();
}
}
class Runnable3 implements Runnable {
private int id;
public Runnable3(int id) {
this.id = id;
}
@Override
public void run() {
System.out.println("执行"+id+"的时间:"+new Date(System.currentTimeMillis()));
}
}
2.延时重复循环
package top.remained.juc.pool.executorservice;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @Author: lx
* @CreateTime: 2023-04-04 18:26
* @Description: TODO 有线程池的特性,也可以实现任务循环执行,可以看作是一个简单地定时任务组件,
* 因为有线程池特性,所以任务之间可以多线程并发执行,互不影响,当任务来的时候,才会真正创建线程去执行
* schedule()可以对任务进行延迟处理
*/
public class ScheduledExecutorServiceTest {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
System.out.println("开启的时间:"+new Date(System.currentTimeMillis()));
// 5为第一次初始化的时间 4为循环周期
pool.scheduleAtFixedRate(new Runnable3(1),5,4,TimeUnit.SECONDS );
pool.scheduleAtFixedRate(new Runnable3(2),5,4,TimeUnit.SECONDS );
}
}
class Runnable3 implements Runnable {
private int id;
public Runnable3(int id) {
this.id = id;
}
@Override
public void run() {
System.out.println("执行"+id+"的时间:"+new Date(System.currentTimeMillis()));
}
}
注意:任务执时间小于任务时间间隔为,否则就会导致任务连续执行,该方法不能严格保证任务按照规定的时间间隔执行,如果你的任务执行时间可以保证忽略不计,则可以使用该方法。
3.严格按照一定时间间隔执行
方法变为
pool.scheduleWithFixedDelay(new Runnable3(1),5,4,TimeUnit.SECONDS );
Executors创建线程池的4种方法十分方便,但是构造器创建普通线程池、可调度线程池比较复杂,这些构造器会涉及大量的复杂参数,已经较少使用。
newFixedThreadPool和newSingleThreadExecutor: 阻塞队列无界,会堆积大量任务导致OOM(内存耗尽)
newCachedThreadPool和newScheduledThreadPool: 线程数量无上界,会导致创建大量的线程,从而导致OOM
建议直接使用线程池ThreadPoolExecutor的构造器
package top.remained.juc.pool;
/**
* @Author: lx
* @CreateTime: 2023-03-31 17:39
* @Description: TODO
*/
public class MyTask implements Runnable {
private int id;
// run不能带参数 所以初始化的时候把id初始化成功
MyTask(int id) {
this.id = id;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+"即将执行任务:"+id);
try {
Thread.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(name+"完成了任务:"+id);
}
@Override
public String toString() {
return "MyTask{" +
"id=" + id +
'}';
}
}
package top.remained.juc.pool;
import java.util.List;
/**
* @Author: lx
* @CreateTime: 2023-03-31 17:42
* @Description: TODO 编写线程类
*/
public class MyWorker extends Thread{
private String name;
private List<Runnable> takes ;
public MyWorker( String name, List<Runnable> takes) {
super(name);
this.takes = takes;
}
/**
* @description: 一直执行的原因:线程1完成任务时 (因为中途休眠了20ms)task的长度已经达到20 当然也有意外,就是你线程执行任务了 task的长度还没增加。
* 即剩下的任务虽然被缓存 但不会有线程去执行该任务 解决方法:让主线程执行加快或令线程1执行变慢。
* @author: lmk
* @date: 2023/4/4 15:04
* @param: []
* @return: void
**/
@Override
public void run() {
// 判断集合中是否有任务 只要有就一直执行
while (takes.size() > 0) {
// 移除第一个任务 返回的是移除的对象 即 Task
Runnable r = takes.remove(0);
// 一直执行 一直执行的是task里的任务
r.run();
}
}
}
package top.remained.juc.pool;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* @Author: lx
* @CreateTime: 2023-03-31 17:52
* @Description: TODO 自定义线程池类
*
*/
public class MyThreadPool {
/**
* @description:
* @author: lmk
* @date: 2023/3/31 17:53
* @param: 1. 2.,3.。4.。5.
* @return:
**/
private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());//任务队列(用线程安全的)
private int num;//当前线程数量
private int corePoolSize;//核心线程数量
private int maxPoolSize;//最大线程数量
private int workSize;//任务队列的长度
public MyThreadPool(int corePoolSize, int maxPoolSize, int workSize) {
this.corePoolSize = corePoolSize;
this.maxPoolSize = maxPoolSize;
this.workSize = workSize;
}
// 提交任务
public void submit(Runnable runnable){
// 判断当前集合中任务的数量是否超出最大任务数
if (tasks.size() >= workSize) {
System.out.println("任务" + runnable + "被丢弃");
}else {
tasks.add(runnable);
int a = 0;
System.out.println(a++);
// 执行任务
execTask(runnable);
}
}
// 执行任务
private void execTask(Runnable runnable) {
// 判断当前线程数量是否超出核心线程数
if (num <corePoolSize){
new MyWorker("核心线程:"+num,tasks).start();
num ++;
}else if (num<maxPoolSize){
new MyWorker("非核心线程:"+num,tasks).start();
num ++;
} else {
System.out.println("任务" + runnable + "被缓存");
}
}
}
package top.remained.juc.pool;
import static java.lang.Thread.currentThread;
import static java.lang.Thread.sleep;
/**
* @Author: lx
* @CreateTime: 2023-03-31 18:04
* @Description: TODO 测试
*/
public class MyTest {
public static void main(String[] args) throws InterruptedException {
// 1.创建线程池类
MyThreadPool pool = new MyThreadPool(2, 4, 20);
// 2.提交多个任务
for (int i = 0; i <20 ; i++) {
// 3.创建任务对象,并提供给线程池
MyTask task = new MyTask(i);
pool.submit(task);
}
}
}
void execute(Runnable command): Executor接口中的方法
Future submit(Callable task);
Future submit(Runnable task, T result);
Future> submit(Runnable task);
这3个submit方法都是ExecutorService接口中的方法
两种方法的区别:
execute()方法只能接收Runnable类型的参数,而submit()方法可以接收Callable、Runnable两种类型的参数
Callable类型的任务是可以返回执行结果的,而Runnable类型的任务不可以返回执行结果
submit()提交任务后会有返回值,而execute()没有
submit()方便Exception处理
异步计算结果
package top.remained.juc.pool.executorservice;
import java.util.concurrent.*;
/**
* @Author: lx
* @CreateTime: 2023-04-04 10:18
* @Description: TODO Future 异步计算结果
*/
public class FutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
// 1.获取线程池对象
ExecutorService pool = Executors.newCachedThreadPool();
// 2.创建Callable类型的任务对象
Future<Integer> future = pool.submit(new Callable1(1, 2));
System.out.println(future.isDone());//是否完成
System.out.println(future.isCancelled());//是否取消
// boolean cancel = future.cancel(true); 取消 取消成功返回true
Thread.yield();
System.out.println(future.toString());
System.out.println(future.get());//得到结果 一直等待
System.out.println(future.get(2,TimeUnit.SECONDS));//在规定时间内得到结果
}
}
class Callable1 implements Callable<Integer> {
private int a;
private int b;
public Callable1(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"计算");
System.out.println(Thread.currentThread().getName()+"计算完成");
return a+b;
}
}
execute(Runnable task): 向线程池提交一个任务,让线程池执行。
submit(Callable task): 向线程池提交一个Callable任务,返回一个Future对象,可以通过Future对象获取任务执行结果。
shutdown(): 关闭线程池,不再接受新的任务,等待已经提交的任务执行完毕后关闭线程池。
shutdownNow(): 立即关闭线程池,中断正在执行的任务,并返回尚未执行的任务列表。
isShutdown(): 判断线程池是否已经关闭。
isTerminated(): 判断线程池中所有任务是否已经执行完毕。
awaitTermination(long timeout, TimeUnit unit): 等待线程池中所有任务执行完毕,或者超时。
getActiveCount(): 获取线程池中正在执行任务的线程数量。
getCompletedTaskCount(): 获取线程池中已经完成执行的任务数量。
getTaskCount(): 获取线程池中已经提交的任务数量。
getQueue(): 获取线程池中等待执行的任务队列。
getThreadFactory(): 获取线程池中的线程工厂。
setThreadFactory(ThreadFactory factory): 设置线程池中的线程工厂。
getRejectedExecutionHandler(): 获取线程池中的拒绝策略。
setRejectedExecutionHandler(RejectedExecutionHandler handler): 设置线程池中的拒绝策略
创建线程池步骤
1:利用Executors工厂类的静态方法,创建线程池对象;
2:编写Runnable或Callable实现类的实例对象;
3:利用ExecutorService的submit方法或ScheduledExecutorService的schedule方 法提交并执行线程任务
4:如果有执行结果,则处理异步执行结果(Future)
5:调用shutdown()方法,关闭线程池