本文主要介绍了Java并发编程的基础知识,包括线程、进程及其相关的状态、线程的创建方式 等知识及常见问答题
Java-多线程-知识点-并发知识点01
- 多线程&并发
- 线程、进程、协程
- 1、线程(Thread)
- 2、进程(Process)
- 3、协程(Coroutine)
- 线程与进程的区别
- 线程与协程的区别
- 有了进程为什么还要先线程
- 为什么要使用多线程
- 并发、多线程的处理
- JAVA 线程实现/创建方式
- 1、继承 Thread 类
- 2、实现 Runnable 接口
- 3、使用 Callable 接口
- 4、使用匿名内部类
- 使用Runnable实现线程和使用Callable有什么区别
- 线程的状态
- 进程的状态
- 进程状态的切换
- 三态模型
- 五态模型
- 进程间进行通信的方式
- 1、管道(Pipe):
- 优点:
- 缺点:
- 应用场景:
- 2、消息队列(Message Queues):
- 优点:
- 缺点:
- 应用场景:
- 3、信号量(Semaphores):
- 优点:
- 缺点:
- 应用场景:
- 4、信号(Signals):
- 优点:
- 缺点:
- 应用场景:
- 5、共享内存(Shared Memory):
- 优点:
- 缺点:
- 应用场景:
- 6、套接字(Socket):
- 优点:
- 缺点:
- 应用场景:
- 同步机制、异步机制
- 同步机制:
- 异步机制
多线程&并发
Java 中的并发是指在多线程环境下有效地管理和控制多个线程之间的执行,以实现程序的高效运行和资源的合理利用。
Java 提供了丰富的并发编程工具和类库,包括 java.util.concurrent 包下的各种工具和类,以及 synchronized 关键字、volatile 关键字等关键技术。
线程、进程、协程
1、线程(Thread)
- 线程是操作系统能够调度的最小单位,是进程的实际运作单位。可以通过继承 Thread 类或实现 Runnable 接口来创建线程。
- 线程是进程的一部分,是一个独立的执行路径。由进程管理,每个线程都有自己的执行栈、程序计数器等,共享进程的内存空间及资源。
- 线程可以并发执行,通过 start() 方法启动线程执行其 run() 方法中的代码。打开微信,系统新建一个进程。然后我发送和接收信息,就是使用了不同的线程。
2、进程(Process)
- 进程是程序执行的一个实例,它拥有独立的内存空间、系统资源和运行环境。比如,打开微信,系统会创建一个新的进程。退出微信,进程会被终止,释放资源。
- 每个进程都是独立的,拥有自己的地址空间和资源,进程之间需要通过进程间通信(IPC)来实现数据交换和共享。
- 进程之间相互隔离,一个进程的崩溃不会影响其他进程,有利于提高系统的稳定性和安全性。
3、协程(Coroutine)
- 协程是一种用户态的轻量级线程,也称为协作式多任务。它由用户自行控制切换,不依赖于操作系统的调度。
- 协程可以在一个线程内实现多个逻辑执行流,每个协程有自己的执行状态和堆栈,可以暂停和恢复执行,实现非抢占式调度。
- 协程的切换开销非常小,适用于高并发、高吞吐量的场景,能够简化异步编程和复杂的状态管理。
线程与进程的区别
特性 | 进程 | 线程 |
---|---|---|
定义 | 程序执行的一个实例,拥有独立资源 | 线程是进程中的一个实体,最小调度单位 |
资源占用 | 独立内存空间、系统资源 | 共享进程资源,每个线程有独立栈空间 |
通信与同步 | 进程间通信(IPC) | 共享内存、信号量、互斥量等 |
隔离性 | 相互隔离,一个进程崩溃不影响其他进程 | 共享进程资源,一个线程错误可影响整个进程 |
创建销毁开销 | 较大,需要分配和释放大量资源 | 相对较小,共享资源,不需重新分配和释放 |
重点
:
一个进程中可以有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器、虚拟机栈。
进程与进程之间相互独立,但线程不一定,线程与线程可能会相互影响。
线程的开销小,但不利于资源的管理与保护;进程开销大,但利于资源的管理与保护。
多个线程可以并发的执行不同的任务,提高系统的响应和速度。
线程与协程的区别
- 协程就是你可以暂停执行的函数。协程拥有自己的寄存器上下文和栈。
- 一个进程可以有多个线程,一个线程又可以有多个协程。
- 协程是由程序控制的,在用户态执行,能够大幅度提升性能。
- 多个协程是串行执行的,只能在一个线程内运行。
- 线程进程都是同步机制,而协程则是异步。
- 线程是抢占式,而协程是非抢占式的。
有了进程为什么还要先线程
- 进程切换开销比较大,线程切换成本低。
- 线程更轻量,一个进程可以创建多个线程。
- 多个线程可以并发处理不同的任务,高效利用处理器。
- 同一个进程内的线程共享内存和文件,他们之间相互通信无需调用内核。
为什么要使用多线程
使用多线程能提高系统资源利用率、程序性能、改善用户体验、并行计算、实现异步编程、资源共享和任务分配调度。
- 提高系统资源利用率:计算机CPU是多核的,多线程能够充分利用多核处理器和多任务并发执行的特性,提高系统资源的利用率。
- 提高程序性能:通过多线程并发执行任务,可以减少任务的等待时间,提高程序的响应速度和执行效率,加快任务处理速度。
- 并行计算:多线程可以将大型任务分解成多个子任务并行执行,提高计算性能和处理能力,加速计算过程。
- 任务分配和调度:多线程可以实现任务的分配和调度,灵活管理任务执行顺序和优先级,提高任务处理的灵活性和效率。
并发、多线程的处理
并发编程是指在程序中同时处理多个任务或操作的能力。多线程是实现并发编程的一种常见方式,处理并发编程和多线程的关键问题的技术包括同步、线程安全、线程池、通信、并发框架等。
JAVA 线程实现/创建方式
在 Java 中,可以通过多种方式实现和创建线程,主要包括以下几种方法:1、实现Runnable接口;2、实现Callable接口;3、继承Thread类。4、使用匿名内部类。但本质都是实现Runnable接口,实现接口比较好,因为Java不支持多继承,继承了Thread类就不能在继承别的类了。Java支持实现多个接口。
1、继承 Thread 类
实现流程:
- 创建一个类,继承自 java.lang.Thread 类。
- 重写 run() 方法,在 run() 方法中编写线程的执行逻辑。
- 创建线程对象,并调用 start() 方法启动线程。
class MyThread extends Thread {public void run() {// 线程的执行逻辑System.out.println("MyThread running");}
}public class Main {public static void main(String[] args) {MyThread thread = new MyThread();thread.start();}
}
2、实现 Runnable 接口
实现流程:
- 创建一个类,实现 java.lang.Runnable 接口。
- 实现 run() 方法,在 run() 方法中编写线程的执行逻辑。
- 创建线程对象,将实现了 Runnable 接口的对象作为参数传递给 Thread 构造方法,并调用 start() 方法启动线程。
class MyRunnable implements Runnable {public void run() {// 线程的执行逻辑System.out.println("MyRunnable running");}
}public class Main {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();}
}
3、使用 Callable 接口
- 创建实现 Callable 接口的类,并重写 call() 方法,编写任务逻辑并返回结果。
- 创建 ExecutorService 对象,如 ExecutorService executor = Executors.newSingleThreadExecutor();。
- 使用 ExecutorService 的 submit() 方法提交 Callable 对象给线程池,并获取返回的 Future 对象。
- 通过 Future 对象的 get() 方法获取线程执行的结果,可能需要处理异常。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class Main {public static void main(String[] args) {// 创建实现 Callable 接口的类,并实现 call() 方法Callable<Integer> callable = () -> {int sum = 0;for (int i = 1; i <= 10; i++) {sum += i;}return sum;};// 创建 ExecutorService 对象ExecutorService executor = Executors.newSingleThreadExecutor();// 提交 Callable 对象给线程池,并获取返回的 Future 对象Future<Integer> future = executor.submit(callable);try {// 获取线程执行的结果Integer result = future.get();System.out.println("Result: " + result);} catch (Exception e) {e.printStackTrace();}// 关闭 ExecutorServiceexecutor.shutdown();}
}
4、使用匿名内部类
可以直接使用匿名内部类来实现 Runnable 接口或继承 Thread 类,并在内部实现 run() 方法。
public class Main {public static void main(String[] args) {Thread thread1 = new Thread(new Runnable() {public void run() {// 线程的执行逻辑System.out.println("Thread 1 running");}});thread1.start();Thread thread2 = new Thread() {public void run() {// 线程的执行逻辑System.out.println("Thread 2 running");}};thread2.start();}
}
使用Runnable实现线程和使用Callable有什么区别
1、返回值:
- Runnable 接口的 run() 方法没有返回值,因此无法获取线程执行的结果。
- Callable 接口的 call() 方法可以返回一个结果,可以通过 Future 对象获取线程执行的结果。
2、抛出异常:
- Runnable 接口的 run() 方法不能抛出受检查异常,只能抛出未受检查异常。
- Callable 接口的 call() 方法可以抛出受检查异常,需要通过异常处理来处理可能的异常。
3、用途:
- Runnable 适用于简单的线程任务,不需要返回结果或处理异常的情况。
- Callable 适用于需要返回结果或处理异常的线程任务,通常用于异步计算、并行任务等场景。
线程的状态
线程在 Java 中有以下几种状态:
1、新建状态(New):
当线程对象被创建但尚未调用 start() 方法时,线程处于新建状态。
2、运行状态(Running):
当线程被调度执行后,处于运行状态,表示线程正在执行任务。
3、阻塞状态(Blocked):
线程处于阻塞状态时,表示线程暂时停止执行,等待某个条件满足或者等待其他线程释放某个资源。阻塞状态包括多种情况,如等待 I/O 操作、等待获取锁、等待唤醒等。
4、等待状态(Waiting):
线程处于等待状态时,表示线程正在等待其他线程的通知或特定条件的满足。等待状态包括 wait()、join()、sleep() 等方法引起的等待。
5、超时等待状态(Timed Waiting):
超时等待状态与等待状态类似,不同之处在于超时等待状态有一个等待时间,当等待时间到达后线程会自动唤醒。常见的超时等待状态包括 sleep() 方法和带有超时参数的 wait() 方法等。
6、终止状态(Terminated):
线程执行完任务或者发生异常导致线程终止时,线程进入终止状态。
进程的状态
1、新建状态(New):
当进程被创建但尚未被调度执行时,处于新建状态。在此阶段,系统为进程分配资源,但进程还未执行。
2、就绪状态(Ready):
进程已经准备好运行,等待分配CPU时间片,处于就绪状态。
3、运行状态(Running):
系统分配了CPU时间片,进程正在处理器上上运行。
4、阻塞状态(Blocked):
又称为等待状态,进程正在等待某一事件而暂停运行。即使处理器空闲,该进程也不能运行。
5、终止状态(Terminated):
进程执行完任务或者发生异常导致进程终止时,处于终止状态。
进程状态的切换
三态模型
- 运行:占有处理器正在运行
- 就绪:具备运行条件,等待系统分配处理器(CPU时间片)以便运行
- 等待:又称为阻塞(blocked)态或睡眠(sleep)态,不具备运行条件,正在等待某个事件的完成
1、就绪 --> 运行 所需条件:Cpu时间片分配、调度算法选择优先级高的进程、进行上下文切换保存上下文信息(寄存器、程序计数器)、Cpu空闲。
2、运行 – >就绪 条件:时间片用完,等待分配时间片
3、运行 --> 阻塞 条件 :等待某一个事物发生
4、阻塞 --> 就绪 条件:等待的事件发生
五态模型
进程间进行通信的方式
进程间通信(Inter-Process Communication,IPC)是操作系统中重要的概念,不同的进程可以通过多种方式进行通信,常见的方式包括:管道、消息队列、共享内存、信号量、信号、socket。
1、管道(Pipe):
管道是一种单向通信方式,通常用于具有亲缘关系的进程之间进行通信,如父子进程或兄弟进程。管道可以分为匿名管道和命名管道:
- 匿名管道(Anonymous Pipe):由 pipe() 系统调用创建,只能在具有亲缘关系的进程间使用。匿名管道没有名字,用于具有父子进程间或者兄弟进程之间的通信。
- 命名管道(Named Pipe,也称为FIFO):由 mkfifo() 系统调用创建,可以允许不具有亲缘关系的进程进行通信。命名管道严格遵循先进先出(first in first out)。命名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
优点:
简单易用,适用于父子进程或兄弟进程间通信。
可以实现进程间的顺序通信,数据传输有序且不会出现数据竞争。
缺点:
只能用于具有亲缘关系的进程通信。
单向通信,需要使用两个管道实现双向通信。
应用场景:
适用于具有亲缘关系的进程间通信,如父子进程或兄弟进程之间的数据传输。
2、消息队列(Message Queues):
消息队列是一种通过内核中转的通信方式,进程可以通过发送消息到队列和从队列接收消息来实现通信。消息队列具有消息的缓冲特性,适用于异步通信和多个进程之间的通信。
消息队列具有先进先出的特性,进程可以从队列中读取消息,并可以按照优先级对消息进行排序。可以便发送边接收。
管道和消息队列的通信数据都是先进先出的原则。与管道 不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正的删除。
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺点。可以变发送边接收。
优点:
支持多对多的进程通信。
提供消息缓冲区,可以异步通信,降低进程间的耦合性。
缺点:
实现稍复杂,需要考虑消息格式、大小等问题。
对消息的处理需要额外的代码逻辑。
应用场景:
适用于多个进程之间的异步通信,消息格式化较为规范的场景。
3、信号量(Semaphores):
信号量是一种用于进程间同步和互斥的机制,通过对信号量进行操作来实现进程之间的通信和协调。信号量可以用于解决临界区问题、资源共享问题等。
信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
信号量有P操作和V操作:
P 操作是将信号量的值减 1,相减后信号量如果小于0,则表示资源已经被占用了,如果大于等于0,则说明还有资源可用,进程可以正常执行。
V 操作是将信号量的值加 1,相加后信号量如果小于等于0,则表明当前有进程阻塞,于是会将该进程唤醒;如果大于0,则表示当前没有阻塞的进程。
优点:
提供了对临界资源的控制和同步。
可以实现进程之间的互斥访问和同步操作。
缺点:
容易出现死锁等问题,需要谨慎设计和使用。
对临界资源的控制较为粗粒度。
应用场景:
适用于多个进程对临界资源的访问控制和同步操作,如实现生产者-消费者模型。
4、信号(Signals):
信号是一种用于进程间通知和处理异步事件的机制,如进程终止、中断等。进程可以发送信号给其他进程或接收来自操作系统的信号来进行通信和处理。
优点:
提供了进程间通知和处理异步事件的机制。
简单易用,可以快速实现进程间的通信。
缺点:
只能传递信号,不能传递数据。
信号处理较为简单,不适合复杂的通信需求。
应用场景:
适用于进程间的简单通知和事件处理,如进程终止、中断等情况。
5、共享内存(Shared Memory):
共享内存是一种高效的进程间通信方式,多个进程可以映射同一块物理内存区域,实现数据的共享和交换。由于共享内存直接操作内存,因此需要额外的同步机制来保证数据一致性和安全性。
它允许多个进程访问同一块物理内存空间,从而实现数据共享(允许多个进程共享一个内存区域)。不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
优点:
数据共享效率高,可以实现快速的数据交换。
可以直接操作内存,速度快。
缺点:
需要额外的同步机制来保证数据一致性和安全性。
容易出现数据竞争和死锁等问题。
应用场景:
适用于需要高效共享数据的进程间通信,如多个进程共享大量数据的场景。
6、套接字(Socket):
套接字是一种网络编程中常用的通信方式,也可以用于进程间通信,特别是在不同计算机之间的进程通信中。套接字提供了可靠的双向通信机制,适用于跨网络或同一主机上的进程通信。
优点:
支持网络通信,可以在不同计算机之间进行进程通信。
提供了可靠的双向通信机制。
缺点:
数据传输开销较大,适用于跨网络通信,不适合同一主机上的进程通信。
需要考虑网络连接和安全性等问题。
应用场景:
适用于不同计算机之间的进程通信,如客户端-服务器模型。
同步机制、异步机制
同步机制和异步机制是在并发编程中常用的两种任务处理方式,它们在任务执行、等待和通知方面有着不同的特点和应用场景。
同步机制:
- 定义:同步指的是按照顺序执行任务,一个任务执行完成后才能执行下一个任务,任务之间需要等待和协调。
- 特点:任务按照顺序依次执行,需要等待一个任务完成后才能执行下一个任务,通常会阻塞调用者的线程。
- 应用场景:适用于需要严格控制任务执行顺序和依赖关系的场景,如线程同步、资源访问控制等。
异步机制
- 定义:异步指的是任务可以独立执行,不需要等待其他任务完成,任务的完成通常通过回调函数或事件触发来处理。
- 特点:任务可以并发执行,不会阻塞调用者的线程,任务完成后通过回调或事件通知调用者。
- 应用场景:适用于需要提高系统性能和资源利用率的场景,如异步 I/O、事件驱动编程、并发任务处理等。