目录
并发并行,同步异步,线程安全
线程的几种状态
并发三要素?
创建线程的方法?
线程间的通信方式?
进程的通信方式?
多线程的上下文切换?
CAS 算法
并发并行,同步异步,线程安全
-
同步:发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
-
异步:调用在发出之后,不用等待返回结果,该调用直接返回。
-
并发:两个及两个以上的作业在同一 时间段 内执行。
-
并行:两个及两个以上的作业在同一 时刻 执行。
-
线程安全指的是在多线程环境下,对于同一份数据,不管有多少个线程同时访问,都能保证这份数据的正确性和一致性。
-
线程不安全则表示在多线程环境下,对于同一份数据,多个线程同时访问时可能会导致数据混乱、错误或者丢失。
线程的几种状态
并发三要素?
-
原子性:一个或者多个操作要么全部执行成功要么全部执行失败。
-
有序性:程序执行顺序按照代码顺序先后执行,但是CPU可能会对指令进行重排序。
-
可见性:当多个线程访问同一个变量时,如果一个线程修改了变量,其他线程立即获取最新的值。
创建线程的方法?
-
继承Thread类
-
实现Runnable接口
-
实现Callable接口( JDK1.5>= )
-
线程池方式创建
Callable接口类似于Runnable接口,但是它可以返回一个结果,并且可以抛出异常。
继承Thread类的优缺点
优点:编写简单,如果需要访问当前线程,无需使用 Thread.currentThread() 方法,直接使用this即可获取当前线程
缺点:因为线程类已经继承了Thread类,Java语言是单继承的,所以就不能再继承其他父类了。
实现Runnable、Callable接口的优缺点
优点:线程类只是实现了Runnable或者Callable接口,还可以继承其他类。这种方式下,多个线程可以共享一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。
缺点:编程稍微复杂一些,如果需要访问当前线程,则必须使用 Thread.currentThread() 方法
线程间的通信方式?
-
共享内存: 多个线程可以通过访问共享的内存区域来进行通信。线程可以读取和写入共享内存中的数据,从而进行信息的交换和共享。为了保证线程之间的访问顺序和数据的一致性,需要使用同步机制(如锁、volatile关键字)来进行线程间的同步。
-
消息传递
在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信。在 Java 中典型的消息传递方式,就是 wait() 和 notify() ,或者 BlockingQueue 。
-
wait()和notify() / notifyAll(): wait()和notify() / notifyAll()是Object类中定义的方法,用于线程之间的协调和通信。线程可以调用wait()方法进入等待状态,并释放占有的锁,等待其他线程调用notify() / notifyAll()方法唤醒自己。notify()方法会随机选择一个等待的线程进行唤醒,而notifyAll()方法会唤醒所有等待的线程。被唤醒的线程会重新竞争锁,并继续执行。
-
-
阻塞队列(BlockingQueue): 阻塞队列是一种线程安全的队列,支持线程在队列为空或队列已满时进行阻塞等待或唤醒。一个线程可以将数据放入队列中,而另一个线程可以从队列中获取数据。阻塞队列提供了一种高效且线程安全的方式,用于线程之间的数据交换和通信。
进程的通信方式?
-
管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
-
命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
-
消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
-
共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
-
信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
-
套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
-
信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
多线程的上下文切换?
上下文切换是指操作系统在执行多个线程时,将当前线程的上下文(包括寄存器状态、程序计数器、栈指针等)保存起来,然后切换到另一个线程的上下文,使得另一个线程可以继续执行。
上下文切换是由操作系统内核完成的,它可以在不同线程之间实现公平的时间片轮转调度,以实现线程的并发执行。
上下文切换发生的情况有以下几种:
-
时间片耗尽:一个线程的时间片用完后,操作系统会将当前线程的上下文保存起来,并选择下一个要执行的线程进行切换。
-
高优先级线程抢占:当一个高优先级的线程抢占了当前正在执行的低优先级线程时,操作系统会进行上下文切换,以保证高优先级线程尽快执行。
-
阻塞操作:当一个线程执行了阻塞操作(如等待I/O、等待锁、等待信号量等)时,操作系统会将该线程的上下文保存起来,并切换到另一个可执行的线程。
一些减少上下文切换的方法:
-
使用线程池:通过线程池管理线程的创建和复用,避免线程的频繁创建和销毁。
-
减少阻塞操作:使用非阻塞的IO操作、使用无锁数据结构、避免长时间等待等,减少线程的阻塞时间,从而减少上下文切换的次数。
-
合理设置线程优先级:合理设置线程的优先级,使得高优先级线程不会频繁地抢占低优先级线程,减少上下文切换的次数。
-
使用协程或轻量级线程:协程或轻量级线程能够在不切换线程上下文的情况下实现任务的切换,从而减少上下文切换的开销。
CAS 算法
CAS 的全称是 Compare And Swap(比较与交换) ,用于实现乐观锁。CAS 的思想就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。
CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令。
原子操作 即最小不可拆分的操作,也就是说操作一旦开始,就不能被打断,直到操作完成。
CAS 涉及到三个操作数:
-
V:要更新的变量值(Var)
-
E:预期值(Expected)
-
N:拟写入的新值(New)
当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新 V 的值。如果不等,说明已经有其它线程更新了 V,则当前线程放弃更新。