行业知识
多线程万字详解
进���和线程是计算机程序执行的两个重要概念。
1.进程: 进程是操作系统分配资源的基本单位,每个进程都有自己独立的地址空间,每启动一个进程,系统就会为它分配内存。进程间通信比较复杂,需要用到IPC(InterProcess Communication,进程间通信)机制。
进程有五种状态:创建、就绪、运行、阻塞和终止。
2.线程: 线程是进程中的一个执行单元,一个进程可以包含多个线程,它们共享进程的地址空间和资源。线程间通信比进程间通信要简单很多,因为它们可以直接读写进程数据段(如全局变量)来进行通信。
线程也有五种状态:创建、就绪、运行、阻塞和终止。
3.进程和线程的区别:
-
独立性:进程间的内存空间是独立的,而线程共享进程的内存空间。
-
资源消耗:创建或销毁进程时,系统性能开销明显,而线程的资源消耗远小于进程。
-
通信方式:进程间通信需要使用IPC机制,而线程可以直接读写全局变量进行通信。
-
影响:一个进程崩溃后,其他进程不受影响;而一个线程崩溃,会导致整个进程崩溃。
4.多线程和多进程的选择: 在需要进行频繁通信的情况下,多线程更有优势,因为它们可以直接读写内存进行通信。而在需要大量计算且相互独立的情况下,多进程可能更合适,因为它们不会因为一个进程的崩溃而影响其他进程。
并发和并行
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在多个CPU上同时执行。
实现多线程方式一:继承Thread类
方法介绍
方法名 说明 void run() 在线程开启后,此方法将被调用执行 void start() 使此线程开始执行,Java虚拟机会调用run方法() 实现步骤
-
定义一个类MyThread继承Thread类
-
在MyThread类中重写run()方法
-
创建MyThread类的对象
-
启动线程
-
代码演示
public class MyThread extends Thread{ @Override public void run() { for (int i = 0; ipublic class ThreadDemo { public static void main(String[] args) { /* 多线程第一种启动方式 1.自己定义一个类继承Thread 2.重写run方法 3.创建子类的对象,并启动线程 */ MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.setName("线程一"); t2.setName("线程二"); //开启线程 t1.start(); }}实现多线程方式二:实现Runnable接口
Thread构造方法
方法名 说明 Thread(Runnable target) 分配一个新的Thread对象 Thread(Runnable target, String name) 分配一个新的Thread对象 实现步骤
-
定义一个类MyRunnable实现Runnable接口
-
在MyRunnable类中重写run()方法
-
创建MyRunnable类的对象
-
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
-
启动线程
public class MyRun implements Runnable{ @Override public void run() { for (int i = 0; ipublic class ThreadDemo { public static void main(String[] args) { /* 多线程第二种启动方式 1.自己定义一个类继承Runnable接口 2.重写run方法 3.创建自己类的对象 4.创建一个Thread类的对象,并开启线程 */ //创建MyRun的对象 //表示多线程要执行的任务 MyRun mr = new MyRun(); //创建线程对象 Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); t1.setName("线程一"); t2.setName("线程二"); //开启线程 t1.start(); }}实现多线程方式三: 实现Callable接口
方法介绍
方法名 说明 V call() 计算结果,如果无法计算结果,则抛出一个异常 FutureTask(Callable callable) 创建一个 FutureTask,一旦运行就执行给定的 Callable V get() 如有必要,等待计算完成,然后获取其结果 实现步骤
-
定义一个类MyCallable实现Callable接口
-
在MyCallable类中重写call()方法
-
创建MyCallable类的对象
-
创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
-
创建Thread类的对象,把FutureTask对象作为构造方法的参数
-
启动线程
-
再调用get方法,就可以获取线程结束之后的结果。
代码演示
public class MyCallable implements Callable { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i { synchronized (lock2) { System.out.println("Thread 2: Holding lock 2..."); try { Thread.sleep(100); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread 2: Waiting for lock 1..."); synchronized (lock1) { System.out.println("Thread 2: Holding lock 1 & 2..."); } } }); thread1.start(); thread2.start(); }}生产者消费者
概述
生产者消费者模式是一种经典的多线程协作模型,它涉及两类线程:生产者和消费者。
生产者:负责生产数据或对象,并将它们放入一个共享的队列中。生产者不直接与消费者交互,而是将产品放入队列后继续生产,这样可以保证生产者的效率不会因为等待消费者而降低。 消费者:负责从队列中取出数据或对象并进行消费。消费者不需要等待生产者生产,只需检查队列中是否有产品可消费。这样,消费者可以保持持续的工作状态,提高整体的处理效率。
此外,在实现生产者消费者模式时,通常需要考虑以下几个关键点:
- 共享队列:生产者和消费者通过这个队列进行间接通信。生产者将产品放入队列,消费者从队列中取出产品。
- 同步机制:需要确保当队列满时,生产者停止生产;当队列空时,消费者停止消费。这通常通过使用信号量、锁或其他同步机制来实现。
- 线程通信:在某些情况下,生产者生产了新的产品后,需要通知消费者来消费。这种通信可以通过条件变量或其他同步工具来实现。
总的来说,生产者消费者模式有效地解决了生产者和消费者之间的速度匹配问题,允许两者以不同的速度运行,同时确保数据的安全传输和处理。
方法名 说明 void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 void notify() 唤醒正在等待对象监视器的单个线程 void notifyAll() 唤醒正在等待对象监视器的所有线程 为方便理解采用以下示例
来源://https://www.bilibili.com/video/BV17F411T7Ao/
桌子
public class Desk { /* 作用:控制生产者和消费者的执行 */ //是否有面条 0:没有 1:有面条 public static int foodFlag = 0; //总个数 public static int count = 10; //锁对象 public static Object lock = new Object();}生产者
public class Cook extends Thread{ @Override public void run() { while(true){ synchronized (Desk.lock){ if(Desk.count == 0){ break; }else { //判断桌子上是否有食物 if(Desk.foodFlag == 1){ //如果有,就等待 try { Desk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { //如果没有,就制作食物 System.out.println("厨师做了一碗面条"); //修改桌子上的食物状态 Desk.foodFlag = 1; //叫醒等待的消费者开吃 Desk.lock.notifyAll(); } } } } }}消费者
public class Foodie extends Thread{ @Override public void run() { while(true){ synchronized (Desk.lock){ if(Desk.count == 0){ break; }else { if(Desk.foodFlag == 0){ //用锁的对象调用wait() try { //让当前线程跟锁进行绑定 Desk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { Desk.count--; //如果有,就开吃 System.out.println("吃货正在吃面条,还能再吃"+Desk.count+"碗!!!"); //吃完之后,唤醒厨师继续做 Desk.lock.notify(); Desk.foodFlag = 0; } } } } }}二:阻塞队列完成等待唤醒机制
生产者
public class Cook extends Thread{ ArrayBlockingQueue queue; public Cook(ArrayBlockingQueue queue) { this.queue = queue; } @Override public void run() { while(true){ //不断的把面条放在阻塞队列当中 try { queue.put("面条"); System.out.println("厨师放了一碗面条"); } catch (InterruptedException e) { e.printStackTrace(); } } }}public class Foodie extends Thread{ ArrayBlockingQueue queue; public Foodie(ArrayBlockingQueue queue) { this.queue = queue; } @Override public void run() { while(true){ //不断从阻塞队列中获取面条 try { String food = queue.take(); System.out.println(food); } catch (InterruptedException e) { e.printStackTrace(); } } }}public class ThreadDemo { public static void main(String[] args) { /* 利用阻塞队列完成生产者消费者(等待唤醒机制) //细节:生产者消费者必须使用同一个阻塞队列 */ //1.创建阻塞队列的对象 ArrayBlockingQueue queue = new ArrayBlockingQueue(1); //创建线程的对象,并把阻塞队列传递过去 Cook c = new Cook(queue); Foodie f = new Foodie(queue); c.start(); f.start(); }}线程状态
线程池
线程池是一种用于管理线程的资源池,它可以显著提高多线程程序的性能和可靠性。
线程池的实现基于生产者-消费者模式,任务提交方作为生产者将任务放入线程池,而线程池中的工作线程作为消费者来执行这些任务。以下是线程池的一些关键特点:
- 线程复用:线程池通过重用现有线程来执行新任务,减少了频繁创建和销毁线程所带来的开销。
- 任务管理:线程池内部通常有一个工作队列,用于存储等待执行的任务,这样可以实现任务的有序执行。
- 性能提升:通过减少线程创建的开销和优化线程调度,线程池能够提高程序的响应速度和吞吐量。
- 资源控制:线程池可以限制同时运行的线程数量,防止系统过载,同时也可以减少上下文切换的开销。
- 编程简化:使用线程池可以简化多线程编程,开发者只需关注任务的实现,而不必处理线程的创建和管理细节。
- 异步执行:线程池支持异步执行任务,可以在不阻塞主线程的情况下执行耗时操作,提升用户体验。
- 灵活性:现代编程语言如C++提供了丰富的API和库来支持线程池的实现,使得开发者可以根据需要定制线程池的行为。
- 并发控制:线程池可以帮助开发者更好地控制并发级别,避免资源竞争和***锁等问题。
综上所述,线程池是一种高效的线程管理机制,它通过池化技术优化了线程的创建、管理和调度,为并发编程提供了强大支持。在实际应用中,合理地使用线程池可以带来显著的性能提升和更好的资源利用率。
线程池代码实现
-
创建线程池
-
提交任务
-
所有的任务全部执行完毕,关闭线程池
线程池-Executors默认线程池
我们可以使用Executors中所提供的静态方法来创建线程池
static ExecutorService newCachedThreadPool() 创建一个默认的线程池 static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i = 核心线程数量 60, //空闲线程最大存活时间 TimeUnit.SECONDS,//时间单位 new ArrayBlockingQueue(3),//任务队列Executors.defaultThreadFactory(), Executors.defaultThreadFactory(),//创建线程工厂 new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略 ); }}
-
-
-