并发编程
概念
进程和线程和程序
- 进程:
- 线程:
- 程序:
并发和并行
- 并发:
- 并行:
同步和异步
- 同步:需要等待结果返回的,则是同步
- 异步:不需要等待结果返回的,就可以继续运行的就是异步
临界区
- 一个程序运行多个线程本身是没有问题的
- 问题出在多个线程访问共享资源
- 多个线程读共享资源其实也没有问题
- 在多个线程对共享资源读写操作时发生指令交错,就会出现问题
- 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
竞态条件 Race Condition
- 多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
synchroized
- 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
- 作用范围
- 作用方法:锁定对象的实例
- 作用代码块:锁定对象的实例
- 作用静态方法:锁定类
什么情况下线程不安全
- 当多线程操作一个非线程安全的集合
- 多线程下操作一个成员变量
- 在调用安全集合类时需要注意,单个方法是原子的,但是多个方法组合在一起就不是原子的了
java
Hashtable map = new Hashtable();
// 多线程 线程 1 线程 2
new Thread(()->{
if(map.get("key") == null) {
map.put("key", value);
}
}).start();
shell
t1 --> map.get("key") == null
t2 --> map.get("key") == null
t1 --> map.put("key", "2");
t2 --> map.put("key", "1");
// 最后结果不一致
- 不可变变量 String、Integer
- String 每次对 String 的操作都会创建一个新的字符串
- 因为其内部的状态不可以改变,因此它们的方法都是线程安全的
- String 设置 final 之后里面的方法不可以被重写,这样就保证了线程安全
- Spring 里面,如果 Bean 的作用域是默认值 singleton 则线程不安全
- Spring 切面编程,不安全,使用环绕通知。
- 两个类之间的操作,如转账
- 锁类:
- 锁id:
多线程一定能提升效率吗?
- 如果处理器只有一个核心,多线程只会增加线程上下文切换的开销。
- 多核 CPU 可以并行计算,但是能否给应用程序带来显著的性能提升还是要看情况
- IO 操作不占用 CPU,只是我们一般拷贝文件用的是【阻塞IO】,这时相当于线程虽然不用 CPU,但是要等 IO 结束,没能充分利用线程。所以才有 非阻塞 IO 和异步 IO。
并发之共享模型
安全问题案例
- 通过两个线程,同时对变量 a 进行操作,最后结果会有所偏差
java
static volatile int a = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
a++;
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
a--;
}
});
thread.start();
thread2.start();
thread.join();
thread2.join();
System.out.println(a);
}
解决方案
应用之互斥
阻塞式解决方案:synchroized,Lock
注意
虽然java中互斥和同步都可以采用synchronized关键字来完成,但它们还是有区别的:
- 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
- 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点
- synchroized:实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
java
synchronized (对象) { // 线程1,线程2(blocked)
// 临界区
}
java
static volatile int a = 0;
static volatile Object room = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (room) {
a++;
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (room) {
a--;
}
}
});
thread.start();
thread2.start();
thread.join();
thread2.join();
System.out.println(a);
}
- 作用范围
- 作用方法:锁定对象的实例
- 作用代码块:锁定对象的实例
- 作用静态方法:锁定类