多线程
一. 线程与进程
1. 进程
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
2. 线程
(1)是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程。
(2)线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
二. 线程调度
1. 分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
2. 抢占式调度
(1)优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
(2)CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
三. 同步与异步
1. 同步:排队执行 , 效率低但是安全。
2. 异步:同时执行 , 效率高但是数据不安全。
四. 并发与并行
1. 并发:指两个或多个事件在同一个时间段内发生。
2. 并行:指两个或多个事件在同一时刻发生(同时发生)。
五. 多线程实现方法
1. 继承Thread
类继承Thread,重写run方法。run方法就是线程要执行的任务方法。 这个方法里的代码就是一条新的执行路径,执行路径的处理方式不是调用run方法,而是通过Thread对象的start()来启动。
例子:MyThread t = new MyThread();
t.start();
2. 实现Runnable接口
类实现Runnable,重写run方法。创建任务对象(实现Runnable的类),创建线程并分配任务,然后执行。
例子:MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
实现Runnable与继承Thread相比有如下优势:
(1)通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况。
(2)可以避免单继承带来的局限性
(3)任务与线程本身是分离的,提高程序的健壮性
(4)后续学习的线程池,接受Runnable类型的任务,不接受Thread类型的线程。
3. 实现Callable接口
Callable使用步骤
(1)编写类实现Callable接口,实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
(2)创建FutureTask对象,并传入第一步编写的Callable类对象。
FutureTask<Integer> future = new FutureTask<>(callable);
(3)通过Thread,启动线程
new Thread(future).start();
4. Runnable 与 Callable的相同点
(1)都是接口
(2)都可以编写多线程程序
(3)都采用Thread.start()启动线程
5. Runnable 与 Callable的不同点
(1)Runnable没有返回值;Callable可以返回执行结果
(2)Callable接口的call()允许抛出异常;Runnable的run()不能抛出
六. 常用方法
1. Thread.currentThread()
获取当前线程对象
2. getName();
获取线程名称
3. Thread.sleep();
线程休眠
4. 线程阻塞
所有比较消耗时间的操作,例如文件的读取,接受用户输入等,也叫耗时操作。
5. interrupt()线程中断:
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。
6. setDaemon(true)守护线程:
线程:分为守护线程和用户线程
用户线程:当一个进程不包含任何存活的用户线程时,进程结束。
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
7. 线程安全问题:
public static void main(String[] args) {
//线程不安全
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();}
线程不安全解决方案:
(1)同步代码块
格式:synchronized(锁对象){}
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();}
class Ticket() implements Runnable{
private int count = 10;
private Object o = new Object();
public void run(){
while(true){
synchronized( o ){
执行语句
}
}
}
}
锁应该是同一把,就像例子里面,锁的创建是在类的属性创建的而不是在run方法里面创建。
(2)同步方法
把上面的执行语句编写成一个方法,然后给方法添加synchronized修饰符来解决。
class Ticket() implements Runnable{
private int count = 10;
private Object o = new Object();
public void run(){
while(true){
boolean flag = slae();
if(!flag){
break;
}
}
}
}
public synchronized boolean slae(){}
如果不是静态的同步方法,锁就是this,如果是静态的就是 类.class
(3)显式锁Lock
同步代码块和同步方法都是隐式锁
class Ticket() implements Runnable{
private int count = 10;
private Object o = new Object();
Lock l = new ReentrantLock();
public void run(){
while(true){
//加锁
l.lock();
执行语句
//解锁
l.unlock();
}
}
}
8. 公平锁和不公平锁
公平锁:先抢到先执行
不公平锁:一直在抢
隐式锁都是不公平锁,显式锁可以是公平锁,定义:Lock l = new ReentrantLock(true);
9. 线程死锁
A线程被锁住,需要等待B线程结束才能解锁,而B线程也被锁住,需要等待A线程结束才能解锁,两个线程互相等待
10. 多线程通信问题
wait()导致当前线程等待被唤醒,可以添加参数long timeoutMillis,如果没被其他线程唤醒,则经过一段时间被唤醒。
notify()唤醒单个被某个对象监视的线程。
notifyAll()唤醒所有被某个对象监视的线程。
导致等待和唤醒必须是同一个对象
七. 线程的六种状态
(1)New
创建但尚未启动的状态
(2)Runnable
运行状态
(3)Blocked
线程安全,排队状态
(4)Waiting
无限期等待另一个线程唤醒
(5)Time Waiting
等待另一个唤醒或者等待时间结束后自动唤醒
(6)Terminated
结束状态。
八. 线程池
1. 线程池概述
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
2. 线程池的好处
(1)降低资源消耗。
(2)提高响应速度。
(3)提高线程的可管理性。
3. Java中的四种线程池
(1)缓存线程池
判断线程池是否存在空闲线程;
存在则使用;
不存在,则创建线程并放入线程池,然后使用。
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
(2)定长线程池
判断线程池是否存在空闲线程;
存在则使用;
不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用;
不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程。
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
(3)单线程线程池
判断线程池的那个线程是否空闲;
空闲则使用;
不空闲,则等待池中的单个线程空闲后使用。
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
(4)周期性任务定长线程池
判断线程池是否存在空闲线程;
存在则使用;
不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用;
不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程。
周期性任务执行时:
定时执行,当某个时机触发时,自动执行某任务。
参数1. runnable类型的任务
参数2. 时长数字
参数3. 时长数字的单位
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,TimeUnit.SECONDS);
参数1. runnable类型的任务
参数2. 时长数字
参数3. 周期时长(每次执行的间隔时间)
参数4. 时长数字的单位
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,2,TimeUnit.SECONDS);
九. Lambda表达式
调用某个方法需要传递一个对象,而对象继承了某个接口,且这个接口只有一个抽象方法时,就可以使用Lambda表达式
例子:
interface MyMath{
int sum(int x,int y);
}
使用匿名内部类
PrintSum(new MyMath(){
public int sum(int x,int y){
return x+y;
}
},100,200);
public void printSum(MyMath m,int x,int y){
int num = m.sum(x,y);
System.out.println(num);
}
使用Lambda表达式
printSum((int x,int y) -> {
return x+y;
},100,200);
public void printSum(MyMath m,int x,int y){
int num = m.sum(x,y);
System.out.println(num);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?