进程是一个应用程序,而线程是应用程序中的一条顺序流
大多数现代操作系统中,都以线程为基本的调度单位
单个 CPU 将在多个线程之间共享 CPU 的时间片,在给定的时间片内执行每个线程之间的切换:
并发:CPU 在多个任务之间进行切换,状态暂存
并行:
并发出现渊源:
` 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 缓存和内存的一致性问题容易解决
多核状态: 每颗 CPU 都有自己的缓存,这时 CPU 缓存与内存的数据一致性就没那么容易解决。当多个线程在不同的 CPU 上执行时,这些线程操作的是不同的 CPU 缓存
原子性问题
i++ 其实有三个步骤,读取 i 的值,执行 i + 1 操作,然后把 i + 1 得出的值重新赋给 i(将结果写入内存)。 两个线程开始运行 各自读取缓存,然后操作+1 ,结果再把 + 1 之后的值写入内存。因为 CPU 时间片的执行周期是不确定的,所以会出现当 aThread 还没有把数据写入内存时,bThread 就会读取内存中的数据,然后执行 + 1操作,再写回内存,从而覆盖 i 的值,导致 aThread 所做的努力白费。
正常情况:
一旦操作系统在任意 读取/增加/写入 阶段产生线程切换,都会产生线程安全问题。
原子操作要么全部执行,要么全部不执行
volatile 通过保证共享变量的可见性来从侧面对对象进行加锁。可见性的意思就是当一个线程修改一个共享变量时,另外一个线程能够 看见 这个修改的值。
还可以使用
原子类 来保证线程安全
并发模型说的是系统中的线程如何协作完成并发任务。不同的并发模型以不同的方式拆分任务,线程可以以不同的方式进行通信和协作。