并发安全
并发安全
线程安全性
多线程在执行过程中,执行结果总是出乎意料的,无法保证代码的执行正确。
如下是线程不安全的代码,我们的预期执行结果是150000,但是它每次运行的结果都不一样,所以这个代码有线程安全问题。
package com.lyra;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int j = 0; j < 50000; j++) {
i++;
}
});
Thread t2 = new Thread(() -> {
for (int j = 0; j < 50000; j++) {
i++;
}
});
Thread t3 = new Thread(() -> {
for (int j = 0; j < 50000; j++) {
i++;
}
});
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println("执行结果:" + i);
}
}
第一次执行:
第二次执行:
第三次执行
解决线程不安全的问题
线程封闭
出现并发问题的原因一般都是多个线程同时访问一个资源而导致的,那么想要解决线程不安全的问题,那么直接将资源封装到单个线程中只允许单个线程访问即可,我们之前的那个线程不安全的例子就是三个线程同时访问一个资源而导致的,俗话说的好,解决不了问题就解决提出问题的人。
栈封闭
JVM会在每个线程中都新建一个栈空间,每个线程所拥有的栈空间互不影响,所以我们只要将变量设置到方法中就可以保证线程安全,如下例子所示
public static int calculateSum(int[] array) {
int sum = 0; // 局部变量,每个线程调用时都有自己的副本
for (int value : array) {
sum += value; // 只有当前线程能访问这个sum变量
}
return sum; // 返回基本类型值,不会泄露引用
}
// 测试:多个线程同时调用是安全的
public static void main(String[] args) {
int[] data = {1, 2, 3, 4, 5};
// 启动多个线程同时调用
for (int i = 0; i < 5; i++) {
new Thread(() -> {
int result = calculateSum(data);
System.out.println(Thread.currentThread().getName() +
" 计算结果: " + result);
}).start();
}
}
无状态的类
只要保证类中没有成员变量,没有成员变量就无法共享给其他线程,也就不会有线程安全的问题,没有成员变量的类一遍是工具类这种类。如下代码所示
public class Main { // 这个方法是线程安全的,因为sum变量被"栈封闭"
// 测试:多个线程同时调用是安全的
public static void main(String[] args) {
int[] data = {1, 2, 3, 4, 5};
// 启动多个线程同时调用
for (int i = 0; i < 5; i++) {
new Thread(() -> {
Calc calc = new Calc();
int i1 = calc.calculateSum(data);
System.out.println(i1 );
}).start();
}
}
}
class Calc {
public int calculateSum(int[] array) {
int sum = 0; // 局部变量,每个线程调用时都有自己的副本
for (int value : array) {
sum += value; // 只有当前线程能访问这个sum变量
}
return sum; // 返回基本类型值,不会泄露引用
}
}
对象不可变
使用CAS
CAS原子类天然支持原子性,写入失败会进行自旋操作,所以使用CAS或者加锁synchronized关键字可用解决并发不安全的问题。
死锁
现在有两个线程分别是线程A和线程B,线程A想要继续执行下去需要线程B的资源,线程B想要执行下去需要线程A的资源,两个线程互锁,无法执行下去。
虽然道路是曲折的,但前途是光明的。