关注微信公众号 程序员小胖 每日技术干货,第一时间送达!
并发是什么?
标题看起来是不是很蒙,虽然你知道是什么,但是一下子很难用语言清晰的回答出这个问题。
其实很简单举个🌰你就明白了。
为啥你可以一边写代码一边听歌?
为啥你可以一边打台球一边跟别人聊天?
这些行为是不是都属于并发行为?同样的计算机也支持同样的并发操作
并发(concurrency)
是指在某个时间段内,多任务交替的执行任务。当有多个线程在操作时,把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行。 在一个时间段的线程代码运行时,其它线程处于挂起状。
具体怎么实现的呢?它是依赖于cpu的时间片轮转算法实现的呢?
时间片轮转算法(Round Robin Scheduling)
是一种抢占式的调度算法,常用于多道程序设计的操作系统中。它的核心思想是按照顺序将 CPU 时间分成若干个时间片,每个进程在一个时间片内执行,当时间片用完,系统将 CPU 分配给下一个进程。
接下来我们用一段代码演示下该算法
public class RoundRobinScheduler {
private Process[] processes;
private int currentProcess;
public RoundRobinScheduler(Process[] processes) {
this.processes = processes;
this.currentProcess = 0;
}
public Process getNextProcess() {
if (currentProcess >= processes.length) {
currentProcess = 0;
}
Process process = processes[currentProcess];
currentProcess++;
return process;
}
public static void main(String[] args) {
Process p1 = new Process("P1");
Process p2 = new Process("P2");
Process p3 = new Process("P3");
RoundRobinScheduler scheduler = new RoundRobinScheduler(new Process[]{p1, p2, p3});
for (int i = 0; i < 10; i++) {
Process process = scheduler.getNextProcess();
System.out.println("Running process: " + process.getName());
}
}
}
class Process {
private String name;
public Process(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
这个简单的Java代码示例展示了如何实现一个简单的时间片轮转(Round Robin, RR)调度算法。它定义了一个RoundRobinScheduler类,它维护了一个进程数组和一个当前进程的索引。getNextProcess方法用于选择下一个应该运行的进程,并且会在进程数组中循环。这个例子可以作为操作系统中进程调度算法教学的一个简单示例。
并发的特性
原子性
原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。
代码示例
private long count = 0;
public void calc() {
count++;
}
- 将count从主存读到工作内存中的副本中
- +1的运算
- 将结果写入工作内存
- 将工作内存的值刷回主存(什么时候刷入由操作系统决定,不确定的)
那程序中原子性指的是最小的操作单元,比如自增操作,它本身其实并不是原子性操作,分了3步的,包括读取变量的原始值、进行加1操作、写入工作内存。所以在多线程中,有可能一个线程还没自增完,可能才执行到第二部,另一个线程就已经读取了值,导致结果错误。那如果我们能保证自增操作是一个原子性的操作,那么就能保证其他线程读取到的一定是自增后的数据 关键字 synchronized。
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
代码示例
//线程1
boolean stop = false;
while(!stop){
doSomething();
}
//线程2
stop = true;
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
如果线程2改变了stop的值,线程1一定会停止吗?不一定。当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
有序性
虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按 照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对 变量的值没有造成影响,但有可能会出现线程安全问题。
代码示例
int a = 0;
bool flag = false;
public void write() {
a = 2; //1
flag = true; //2
}
public void multiply() {
if (flag) { //3
int ret = a * a;//4
}
}
write方法里的1和2做了重排序,线程1先对flag赋值为true,随后执行到线程2,ret直接计算出结果, 再到线程1,这时候a才赋值为2,很明显迟了一步