线程安全分析与处理
共享模型之管程
线程安全问题 原因
1. 临界区
static int count = 0;
static void increment()
// 临界区
{
count++;
}
static void decrement()
// 临界区
{
count--;
}
2. 竞态条件
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为竞态条件
synchronized解决方案
应用之互斥
wile避免临界区竞态条件发生,有多种条件可以达到目的
- 阻塞式解决方案 :synchronized ,Lock
- 非阻塞时解决方案: 原子变量
注意
虽然java中的互斥和同步都是可以用synchronized关键字来完成,但是他们还是有区别的- 互斥是保证临界区的竞态条件发生,同一时刻只能由一个线程执行临界区代码
- 同步时由于线程执行的先后、顺序不同,需要一个线程等待其他线程运行到某一个点
synchronized
语法
synchronized(对象) // 锁的这个对象
{
临界区
}
synchrobized实际上保证了临界区代码的原子性
public synchronized void test() // 锁的是this对象
{
临界区
}
public static synchronized void test() // 锁的是类对象
{
临界区
}
public static void main(String[] args) { // 两个锁对象 无法互斥
Number n1 = new Number();
Number n2 = new Number();
new Thread(() -> {
log.debug("begin");
n1.a();
}).start();
new Thread(() -> {
log.debug("begin");
n2.b();
}).start();
}
}
@Slf4j(topic = "c.Number")
class Number{
public synchronized void a() { // 锁对象是this
sleep(1); // 两个方法锁对象相同也是互斥的
log.debug("1");
}
public synchronized void b() { // 锁对象是this
log.debug("2");
}
}
synchronized变量的线程安全分析
成员变量和静态变量
- 没有共享,线程安全
- 被共享了 只有读操作安全、有写操作不安全
局部变量
- 局部变量是线程安全的
- 局部变量如果引用的对象没有逃离方法的作用访问,他是线程安全的 ,如果对象逃离方法的作用范围,需要考虑线程安全
线程安全情况
public static void test1()
{
int i = 10; //i没有被线程共享,所以线程安全 局部变量保存在栈帧中 栈帧是线程私有的
i++;
}
线程不安全情况
class ThreadSafe {
public void method1(int loopNumber) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < loopNumber; i++) {
method2(list);
method3(list);
}
}
public void method2(ArrayList<String> list) {
list.add("1");
}
public void method3(ArrayList<String> list) {
list.remove(0);
}
}
class ThreadSafeSubClass extends ThreadSafe{
@Override
public void method3(ArrayList<String> list) {
new Thread(() -> {
list.remove(0); //继承后 初始化线程后 局部变量引用暴漏给了其他线程
}).start();
}
}
private 或final 提供了 【安全的意义所在】,开闭原则的闭