java多线程
概述
充分利用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 | Thread thread = new Thread(() ->{ |
类型
run方法是同步执行(在原有线程执行)
start方法是异步执行(新建一个线程执行)
作用
run方法的作用是是存放任务代码,而start方法是启用线程。
调用次数
run方法可以被执行无数次,而start方法只能被执行一次,原因在于线程不能被重复启动。
1 | Thread.currentThread().getName(); |
public void setName(String name);给当前线程取名,
public void getName();获取当前线程名字。
睡眠
1 | try { |
线程延迟,单位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 | thread.getPriority(); |
public finall int getPriority();返回此线程的优先级
public finall voud setPriority(int priority);设置此线程的优先级(常用1、5、10)java中规定的优先级是1~10的整数,较大的优先级能提高该线程被CPU调度的几率。
1 | thread.start(); |
public void interrupt();实例方法interrupt()仅仅是设置线程的中断状态为true,并不会停止线程
public boolean isInterrupt();通过检查中断标志,判断当前线程是否被中断。
public static boolean interrupted();静态方法,判断线程是否被中断,并清除当前中断状态;
即 1.返回当前线程的中断状态
2.将当前线程的中断状态设置为false
1 | thread.start(); |
public final void join();等待这个线程结束
public final void join(long millis);等待该线程死亡millis毫秒,0意味着永远等待
public final native boolean isActive();线程是否存活(还没运行完毕)
2.3守护进程
1 | thread.setDaemon(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,TERMINATEDNEW:初始状态,线程被构建但是还没有被调用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
corePoolSize:核心线程池数量
maximumPoolSize:最大线程数量
keepAliveTime:(空闲状态非核心的)存活时间
unit:时间单位(天、小时、分钟…)
workQueue:工作队列(阻塞队列)
threadFactory:线程工厂(创建线程)
handler:拒绝策略(有四种:丢任务抛异常,丢任务不抛异常,丢弃最前面的任务然后重新尝试执行任务,由调用线程处理该任务)
线程池关闭
1 | threadPool.shutDown();// 关闭线程池 不会立马停止,等待执行完后才关闭 |
线程池关闭一定要写到finall里。
返回值
1 | threadPool.execute();// Runnable 也可以接受返回值,底层实现了Runnable接口 |
异常
threadPool.execute();会在子线程中抛出异常,主线程捕捉不到,需要自己去写。
threadPool.submit();不会立马抛出异常,而是会暂时保存起来,直到调用Future.get()方法时才抛出异常,并且可以在主线程中捕捉到。
TomCat和JDK的区别
JDK初始化的时候创建的线程池内并没有线程,而TomCat会创建线程(初始化方法内存在线程创建方法)