并发图示

130 阅读2分钟

进程是一个应用程序,而线程是应用程序中的一条顺序流 rr.png

rr.png

uu.png 大多数现代操作系统中,都以线程为基本的调度单位

单个 CPU 将在多个线程之间共享 CPU 的时间片,在给定的时间片内执行每个线程之间的切换: uu.png

并发:CPU 在多个任务之间进行切换,状态暂存 ee.png 并行:

ee.png 并发出现渊源: rr.png

` public class TSynchronized implements Runnable{ static int i = 0;

public void increase(){
    i++;
}


@Override
public void run() {
    for(int i = 0;i < 1000;i++) {
        increase();
    }
}

public static void main(String[] args) throws InterruptedException {

    TSynchronized tSynchronized = new TSynchronized();
    Thread aThread = new Thread(tSynchronized);
    Thread bThread = new Thread(tSynchronized);
    aThread.start();
    bThread.start(); 
    Thread.sleep(3000); 
    System.out.println("i = " + i);
}

可见性问题 :

单核状态: 所有的线程共用一个 CPU,CPU 缓存和内存的一致性问题容易解决 ee.png

多核状态: 每颗 CPU 都有自己的缓存,这时 CPU 缓存与内存的数据一致性就没那么容易解决。当多个线程在不同的 CPU 上执行时,这些线程操作的是不同的 CPU 缓存

rr.png

原子性问题

i++ 其实有三个步骤,读取 i 的值,执行 i + 1 操作,然后把 i + 1 得出的值重新赋给 i(将结果写入内存)。 两个线程开始运行 各自读取缓存,然后操作+1 ,结果再把 + 1 之后的值写入内存。因为 CPU 时间片的执行周期是不确定的,所以会出现当 aThread 还没有把数据写入内存时,bThread 就会读取内存中的数据,然后执行 + 1操作,再写回内存,从而覆盖 i 的值,导致 aThread 所做的努力白费。

uu.png

正常情况:

tt.png

一旦操作系统在任意 读取/增加/写入 阶段产生线程切换,都会产生线程安全问题。 原子操作要么全部执行,要么全部不执行

volatile 通过保证共享变量的可见性来从侧面对对象进行加锁。可见性的意思就是当一个线程修改一个共享变量时,另外一个线程能够 看见 这个修改的值。

image.png 还可以使用原子类 来保证线程安全

image.png

并发模型说的是系统中的线程如何协作完成并发任务。不同的并发模型以不同的方式拆分任务,线程可以以不同的方式进行通信和协作。