多线程与高并发-线程入门(一)

本文深入探讨了Java中的多线程概念,包括定义、特点、状态转换和创建方式。线程池的引入解决了频繁创建线程带来的资源消耗问题,通过ExecutorService进行任务提交。文中还阐述了线程池的执行程序和执行器的角色以及它们的工作原理。最后,讨论了多线程在提升CPU利用率、提高用户体验和解耦耗时操作方面的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系列文章目录

多线程与高并发-多线程(一)



前言

多线程和高并发在大厂面试和在大流量的系统是常涉及到的。老铁们要是不会这个,要想拿高薪,那是没戏的,再说吹牛逼也是需要资本的。

在这里插入图片描述


一 多线程

多线程的就是不同的执行路径。

1.1 定义

多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。

1.2 特点

  • 在java内存模型,系统存在一个主内存, Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory)——工作栈,保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。
  • 多个线程的执行是并发的,在逻辑上“同时。如果系统只有一个CPU,那么真正的“同时”是不可能的。由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的,将会带来线程调度,同步等问题。

1.3 状态

  • 五大状态:新建、就绪、运行、等待/阻塞、死亡。
    在这里插入图片描述
  • 新建状态(New):当线程对象对创建后,即进入新建状态,如:Thread t = new MyThread();
  • 就绪状态(Runnable):当调用线程的start(),即进入就绪状态。处于就绪状态的线程,只是说明此线程做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
  • 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口;
  • 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
    1.waiting阻塞:运行状态中的线程执行wait()、join()方法,使本线程进入到等待阻塞状态;
    2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
    3.timewaiting – 通过调用线程的sleep(time)或join(time)或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

方法解释:

  • sleep(time):Thread的方法,会导致此线程暂停执行,主动让出cpu,cpu去执行其他线程,在sleep指定的时间过后,这个线程重回为就绪状态,如果当前线程进入了同步锁,sleep方法并不会释放锁,只让出了cpu,其他被同步锁挡住了的线程也无法得到执行;
  • yield():主动放弃执行权,就像sleep(0);
  • wait():Object类的方法,只能在同步代码块中才能执行,不然报错。放弃执行权、锁,进入对象等待队列,等待被唤醒
  • this.notify();唤醒其他一个线程,notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁
  • this.notifyAll();唤醒其他多个线程,
  • synchronized(){};默认使用this锁,当线程执行到synchronized的方法的是,拥有了this的锁,其他线程不可以再进入这个方法,但是这个对象this下的其他未同步的方法,其他线程是可以进入的,只是不能获取this的锁
  • join:A线程中调用B线程,等B线程完成后,才能继续用下运行A。

1.4 创建方式

// 1.继承Thread类,重写该类的run()方法。
class MyThread extends Thread {
}
public class ThreadTest {
    public static void main(String[] args) {
                Thread myThread1 = new MyThread();     // 创建一个新的线程 myThread1  此线程进入新建状态
                myThread1.start();                     // 调用start()方法使得线程进入就绪状态
    }
}
// 2.实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。

class MyRunnable implements Runnable {
}
public class ThreadTest {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
                Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
                Thread thread1 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
                thread1.start(); // 调用start()方法使得线程进入就绪状态
        }
    }
}
//3、线程池的方式,其实本质也是通过上面的方式。下面会专门讲线程池的方式,Executors执行器工具类,创建线城池执行器
ExecutorService pool = Executors.newFixedThreadPool(3).execute(new Runnable(){public void run(){}});
ExecutorService pool = Executors.newCachedThreadPool().execute(new Runnable(){public void run(){}});
ExecutorService pool = Executors.newSingleThreadExecutor().execute(new Runnable(){public void run(){}});
ExecutorService pool = Executors.newScheduledThreadPool(3).execute(new Runnable(){public void run(){}});

pool.execute(runnable);//提交任务,无返回值
Future future = pool.submit(callable);//提交任务,有返回值

二 线程池

出现原因: 每次都重新创建线程,1、耗费资源,2、可能会发生线程量激增,造成资源耗尽。通过线程池的方式,不用每次新建,还可以控制线程数量。

2.1 组成角色

区分于直接new thread().start()的方式,分离执行程序和执行器。通过创建执行器来执行执行程序

执行程序:

  • Runnable:线程执行体
  • Callable:线程执行体、可异步得到执行结果。
    Future:返回线程执行的对象,通过它可得到线程的执行结果、取消线程执行,针对于执行callable来说的。

执行器:

Executor //父
——>	ExecutorService //子
	——>	AbstractExecutorService //孙子 
		——>	ThreadPoolExecutor //重孙子, 线程池执行器
	——>	ScheduledExecutorService //孙子

ps:通过创建执行器来执行执行程序

ExecutorService pool = Executors.newFixedThreadPool(3).execute(new Runnable(){public void run(){}});
ExecutorService pool = Executors.newCachedThreadPool().execute(new Runnable(){public void run(){}});
ExecutorService pool = Executors.newSingleThreadExecutor().execute(new Runnable(){public void run(){}});
ExecutorService pool = Executors.newScheduledThreadPool(3).execute(new Runnable(){public void run(){}});

pool.execute(runnable);//提交任务,无返回值
Future future = pool.submit(callable);//提交任务,有返回值

Executors四个方法创建的方式底层都是调用的new ThreadPoolExecutor

new ThreadPoolExecutor(
	int corePoolSize,//核心线程数(合同工)
	int maximumPoolSize,//最大线程数
	long keepAliveTime,//临时工线程存活时长
	TimeUnit unit,//时间单位
	BlockingQueue<Runnable> workQueue,//阻塞队列
	ThreadFactory threadFactory,//线程工厂
	RejectedExecutionHandler handler//拒绝后处理类
)

/**
**	pool.submit(callable)  底层
**	把callable包装成futureTask,扔到任务队列中,等着被执行
**/
new FutureTask<T>(callable)
/**
**	pool.execute(runnable);//提交任务,无返回值  ,底层
**	把runnable包装成ForkJoinTask,扔到任务队列中,等着被执行
**/
new ForkJoinTask.RunnableExecuteAction(task);

ThreadPoolExecutor 执行路径:
在这里插入图片描述


1、为什么需要使用多线程?

(1)多CPU多核系统中,使用线程提高CPU利用率。
(2)多个任务同步进行,提高用户体验。
(3)耗时的操作使用线程,提高应用程序响应


我向你奔赴而来,你就是星辰大海

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值