一、基础概念
1.1 进程与线程
什么是进程?
进程是指运行中的程序 比如我们使用钉钉,需要启动这个程序,操作系统会给这个程序分配一定的资源(占用内存资源)
什么是线程?
线程是CPU调度的基本单位,每个线程执行的都是某一个进程代码的某个片段
进程和线程的区别
- 根本不同:进程是操作系统分配的资源,线程是CPU调度的基本单位
- 资源方面:同一个进程下的线程共享进程中的一些资源,线程同时拥有自身的独立存储空间,进程之间的资源通常是独立的
- 数量不同:进程一般值的是一个进程,而线程是依附于某个进程的,而且一个进程中至少会有一个或者多个线程
- 开销不同:进程和线程不是一个级别的内容,线程的创建和终止的时间比较短,而且线程之间的切换比进程之间的切换速度要快很多,而且进程之间的通讯很麻烦,一般要借助内核才可以实现,而线程之间通讯,相当方便
1.2 串行,并发,并行
什么是串行?
串行就是一个一个排队,第一个做完,第二个才能上
什么是并行?
并行就是同时处理(一起上)
什么是并发?
这里的并发并不是三高中的高并发,这里是多线程中的并发概念(CPU调度线程的概念)CPU在极短的时间内,反复切换执行不同的线程,看似好像是并行,但是只是CPU告诉的切换
并行囊括并发
并行就是多核CPU同时调度多个线程,是真正的多个线程同时执行
单核CPU无法实行并行效果,单核CPU是并发
1.3 同步异步,阻塞非阻塞
同步与异步:执行某个功能后,被调用者是否会主动反馈信息
阻塞与非阻塞:执行某个功能后,调用者是否需要一直等待结果的反馈
两个概念看似相似,但是侧重点是完全不一样的
同步阻塞: 比如用锅烧水,水开后,不会主动通知你,烧水开始执行后,需要等待水烧烤
**同步非阻塞:**比如用锅烧水,水开后,不会主动通知你,烧水开始执行后,不需要一直等待水烧开,可以去执行其他功能,但是需要是不是的查看水开了没
**异步阻塞:**比如用水壶烧水,水开后,会主动通知你水烧开了,烧水开始执行后,需要一直等待水烧开
**异步非阻塞:**比如用水壶烧水,水开后,会主动通知你水烧开了。烧水开始执行后,不需要一直等待水烧开,可以去执行其他功能
异步非阻塞这个效果是最好的,平时开发时,提升效率最好的方式就是采用异步非阻塞的方式处理一些多线程任务
二、并发线程的三大特性
一、原子性
1.1 什么是并发线程的原子性
JMM(java内存模型)。不同的硬件和不同的操作系统在内存上的操作有一定差异,java为了解决相同代码在不同操作系统上出现的各种问题,用JMM屏蔽掉各种硬件和操作系统带来的差异。让java的并发编程可以做到跨平台
JMM规定所有变量都会存储在主内存中,在操作的时间,需要从主内存中复制一份到线程内存(CPU内存),在线程内部做计算,然后再写回到主内存中(不一定)
原子性的定义:原子性指一个操作是不可分割,不可中断的,一个线程在执行时,另一个线程不会影响到他
并发编程的原子性代码阐述
private static int count;
public static void increment(){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
count++;
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
当前程序:多线程操作共享数据时,预期的结果,与最终的结果不符
原子性:多线程操作临界资源,预期的结果与最终结果一致
通过对这个程序的分析,可以看出,++的操作,一共分为了三步
1、线程从主内存中拿到数据保存在CPU寄存器中
2、寄存器进行+1操作
3、最终将结果协会主内存中
1.2 保证并发编程的原子性
1.2.1 synchronzied
因为++操作可以从指令中查看到
可以在方法上追加synchronzied关键字或者采用同步代码块的形式来保证原子性
synchronzied可以避免多线程同时操作临界资源,同一时间点,只会有一个线程正在操作临界资源
1.2.2 CAS
什么时CAS?
compare and swap也就是比较和交换,他是一条CPU的并发原语
他在替换内存的某个位置的值时,首先查看内存中的值和预期值是否一致,如果一致,执行替换操作,这个操作时一个原子性操作
Java中基于Unsafa类提供了对CAS操作的方法,JVM会帮助我们将方法实现CAS汇编指令
但是要清楚CAS只是比较和交换,在获取原值的这个操作上,需要自己是实现
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new