概述

充分利用CPU多核特点,减少CPU的空闲时间,发挥它的运算能力,提高并发量。

1.1 进程

进程是基本的资源分配单位,操作系统通过进程来管理计算机的资源,如CPU、内存、磁盘等每个进程都有唯一的进程标识符(PID),用于区分不同的进程。

1.2 线程

线程是操作系统中的基本执行单位。一个进程可以包含多个线程,每个线程都可独立执行不同的任务,但他们共享进程的资源。
CPU和线程之间是1:1的关系,但是Intel引入超线程技术后,产生了逻辑处理器的概念,使核心数与线程数形成1:2的关系。(逻辑处理器和线程之间1:1)
同一时刻,一个CPU核心只能运行一个线程。

1.3 纤程(协程)

多个纤程可以轮流使用同一个线程。 Java 19之后才支持虚拟线程(纤程)。

总结

1.先有进程,然后进程创建线程,线程是依附于进程里的,线程里面包含多个协程。
2.进程之间不共享全局变量,线程之间共享全局变量,线程之间要注意资源竞争问题。

并发 并行 串行

并发(Concurrent)允许两个任务彼此⼲扰。同⼀时间点、只有⼀个任务运⾏,在一个时间段内任务交替执⾏。
并⾏(Parallel)在时间上是重叠的,两个任务在同⼀时刻互不⼲扰的同时执⾏。
串⾏(Serial)在时间上不可能发⽣重叠,前面⼀个任务没执行完成,下⼀个任务就只能等待。

2.1 start和run

单核CPU可以支持多线程处理。(通过时间片)
新建线程后默认为异步线程。
线程实现时可以看其接口是否支持函数表达式。(类似于js,C#和Android)
推荐用myRunnable接口,没有继承的限制。

1
2
3
4
5
6
7
Thread thread = new Thread(() ->{
log.info("子线程启动。");
});
log.info("开始创建线程。");
thread.run();
thread.start();
log.info("主线程结束。");

类型

run方法是同步执行(在原有线程执行)
start方法是异步执行(新建一个线程执行)

作用

run方法的作用是是存放任务代码,而start方法是启用线程。

调用次数

run方法可以被执行无数次,而start方法只能被执行一次,原因在于线程不能被重复启动。

1
2
Thread.currentThread().getName();
thread.setName("线程1");

public void setName(String name);给当前线程取名,
public void getName();获取当前线程名字。

睡眠

1
2
3
4
5
try {
thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

线程延迟,单位1000ms(不常用,测试用,模拟业务执行)
InterruptedException线程中断异常。
其他线程可以通过使用interrupt方法打断正在睡眠的线程,这时候sleep会抛出InterruptedException异常。
建议用TimeUnit的sleep代替Thread的sleep,能提高可读性。(底层的方法一致)
循环访问中可以加入sleep让线程阻塞时间,防止大量占用CPU资源。

2.2线程的让步

1
Thread.yield();

public static native void yield();
Thread.yield();方法作用是:暂停当前线正在执行的线程对象(及放弃当前拥有的CPU资源),并执行其他线程。
yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,yield()的目的是让相同优先级的线程之间能适当的轮转执行,但实际上无法保证yield()达到让步的目的,因为让步线程还有可能被线程调度程序再次选中。
yield()方法不能保证线程一定会让出CPU资源,他只是一个提示,告诉调度器当前线程愿意让出CPU资源,具体是否让出CPU资源,还是由调度器决定。

1
2
3
thread.getPriority();
// MIN_PRIORITY = 1 MORM_PRIORITY = 5 MAX_PRIORITY = 10
thread.setPriority(Thread.MIN_PRIORITY);

public finall int getPriority();返回此线程的优先级
public finall voud setPriority(int priority);设置此线程的优先级(常用1、5、10)java中规定的优先级是1~10的整数,较大的优先级能提高该线程被CPU调度的几率。

1
2
3
thread.start();
// 只是通知线程需要中断,线程不会马上中断,只是给线程做了个标记。
thread.interrupt();

public void interrupt();实例方法interrupt()仅仅是设置线程的中断状态为true,并不会停止线程
public boolean isInterrupt();通过检查中断标志,判断当前线程是否被中断。
public static boolean interrupted();静态方法,判断线程是否被中断,并清除当前中断状态;
即 1.返回当前线程的中断状态
2.将当前线程的中断状态设置为false

1
2
3
thread.start();
//thread线程执行完后才继续向下执行
thread.join();// 主线程等待子线程执行完成后再继续执行;也可以设置参数,等待多少毫秒后就不等了 例如:thread.join(1000);

public final void join();等待这个线程结束
public final void join(long millis);等待该线程死亡millis毫秒,0意味着永远等待
public final native boolean isActive();线程是否存活(还没运行完毕)

2.3守护进程

1
2
thread.setDaemon(true);//设置为守护进程。
Thread.isDaemon();//判断进程是否为守护进程,返回如果为true则为守护线程,反之则为用户进程。

public final void setDaemon(boolean on);将此线程标记为守护线程(默认情况下创建的线程都是用户线程,即普通线程),进程需要等待所有线程执行完毕后,进程才会结束。
守护进程:当所有用进程结束后,守护进程会立马结束;
tomcat用来接受处理外部的请求的线程就是守护进程。

2.4线程状态

public long getId(); 获取长整型的线程id
public state getState(); 获取线程状态 java中的线程状态由6种枚举 NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED
NEW:初始状态,线程被构建但是还没有被调用start()方法; RUNNABLE:运行状态,java线程操作系统中的就绪和运行两种状态统称为“运行中” BLOCKED:阻塞状态,表示线程阻塞于锁 WAITING:等待状态,表示该线程进入等待状态,进入该状态表示当前线程需要其他线程通知(notify或noitfyAll) TIMED_WAITING:超时等待状态,可以指定等待时间自己返回 TERMINATED:终止状态,表示当前线程已经执行完毕
BLOCKED->RUNNABLE 获得monitor锁 (syncchronized 锁)
WAITING->RUNNABLE 需要把等待的状态解除掉 (Wait(),join(),LockSupport.park())
TIMED_WAITING->RUNNABLE 需要把等待时间释放掉或者手动释放掉(sleep(time),Wait(tiemout),LockSupport.parkNanos(time),LockSupport.parkUntil(time),join(timeout))

总结

线程从New状态是不可能直接进入Blocked状态的,必须先经理Runnable状态;
线程周期不可逆,一旦进入Runnable状态就不能回到New状态,一旦被终止就不可能有任何状态变化;
一个线程只能有一次New和Termminated状态,只有处于中间态(Runnable,Blicked,Waiting,Timed_Waiting)才可以相互转换。

3.1Callable接口

Runnable接口和Therad类实现线程时时需要重写run方法,但是run方法不能 return返回结果,此时需要Callable接口。
Thread只能接受Runnable接口的对象,不能直接对接Callable对象,一般使用FutureTask类对Callable对象进行包装。
Callable是异步运行的。

3.2线程创建的方式

1.Runnable接口实现run方法。(无返回值)
2.继承Thread类并重写run的方法。
3.使用FatureTask方法(实现Callable接口的方式 有返回值)

3.3线程池

线程池是一个容纳多个线程的容器,其中的线程可以反复使用,省去频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
作用:线程池做的工作是控制运行的线程数量。
特点:1.降低资源消耗。
2.提高响应速率。
3.提高线程的可管理性。
线程池必须关闭,不然线程池中的线程会一直运行,造成内存泄露。

1
ExecutorService executorService = new ThreadPoolExecutor(10, 20, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue,ThreadFactory threadFactory, RejectedExecutionHandler handler);
corePoolSize:核心线程池数量
maximumPoolSize:最大线程数量
keepAliveTime:(空闲状态非核心的)存活时间
unit:时间单位(天、小时、分钟…)
workQueue:工作队列(阻塞队列)
threadFactory:线程工厂(创建线程)
handler:拒绝策略(有四种:丢任务抛异常,丢任务不抛异常,丢弃最前面的任务然后重新尝试执行任务,由调用线程处理该任务)

线程池关闭

1
2
3
4
5
6
7
8
threadPool.shutDown();// 关闭线程池  不会立马停止,等待执行完后才关闭
threadPool.shutDownNow();// 只会等待当前正在执行的
try{
threadPool.awaitTermination(Long.MAX_VALUE,TimeUnit.SECONDS); // 等待线程池中所有的线程执行完。
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
threadPool.isTermained(); //检测线程池是否真正关闭

线程池关闭一定要写到finall里。

返回值

1
2
threadPool.execute();// Runnable 也可以接受返回值,底层实现了Runnable接口
threadPool.submit();// callable 一定有返回值

异常

threadPool.execute();会在子线程中抛出异常,主线程捕捉不到,需要自己去写。
threadPool.submit();不会立马抛出异常,而是会暂时保存起来,直到调用Future.get()方法时才抛出异常,并且可以在主线程中捕捉到。

TomCat和JDK的区别

JDK初始化的时候创建的线程池内并没有线程,而TomCat会创建线程(初始化方法内存在线程创建方法)