一、多线程安全性问题

2019-12-28  本文已影响0人  kar_joe

本系列文章是极客时间课程的读书总结。

背景

cpu、内存、IO速度差异过大,如何均衡速度,提高系统综合性能?

线程安全性定义

在多线程访问时,程序依旧表现正常,符合预期

线程安全性问题分类

线程安全性问题出现场景:多线程同时读写共享的、可变的数据。
问题主要有一下几类:

  1. 原子性问题
    原子性操作定义:一个或多个操作在CPU执行过程中无法被中断的特性
    问题原因:多线程并发
    示例:count+=1;long/double类型变量读写
    解决办法:临界区加锁保护,保证单线程访问,其他线程阻塞等待,保证中间状态不可见
  2. 可见性问题


    1.png

    可见性问题定义:线程A对共享数据的修改,是否能立即被线程B看到的问题
    问题原因:缓存
    解决办法:按需禁用缓存

  3. 有序性问题
    问题原因:编译器/执行器为优化性能,提高对缓存的利用,会在不改变单线程执行结果的情况下调整执行顺序
    解决办法:按需禁用编译器执行器优化
    举例:
public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}

理想顺序:开辟内存->初始化->赋值引用;
优化后顺序:开辟内存->赋值引用->初始化
解决办法:instance改为volatile变量

  1. 举例
class Example {
  int x = 0;
  boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      // 这里 x 会是多少呢?
    }
  }
}

Java提供哪些能力解决多线程安全性问题

volatile、synchronized 和final关键字,以及六项 Happens-Before 规则

  1. final
    生而常量,随便优化,注意逸出问题
  2. volatile
    保证可见性、有序性,不保证原子性
    volatile禁用缓存以及编译优化,强制刷内存(volatile变量本身以及之前的其他数据更新都强制刷内存);另有一条heppen规则加强volatile语义(volatile变量写happen-before读)
int a = 1;
int b = 2;
volatile int c = 3;
int d = 4;
int e = 5;
  1. synchronized
    解决可见性、有序性、原子性问题
    临界区加锁保护,保证单线程访问,其他线程阻塞等待,保证中间状态不可见
//注意组合操作,要加锁
class Account {
  private int balance;
  // 转账
  void transfer(
      Account target, int amt){
    if (this.balance > amt) {
      this.balance -= amt;
      target.balance += amt;
    }
  }
}
class Account {
  private int balance;
  // 转账
  synchronized void transfer(
      Account target, int amt){
    if (this.balance > amt) {
      this.balance -= amt;
      target.balance += amt;
    }
  }
}
//是否保证了线程安全?

等待-通知模型


2.png

wait与sleep区别在于:

class Allocator {
  private List<Object> als;
  // 一次性申请所有资源
  synchronized void apply(
    Object from, Object to){
    // 经典写法
    while(als.contains(from) ||
         als.contains(to)){
      try{
        wait();
      }catch(Exception e){
      }   
    }
    als.add(from);
    als.add(to);  
  }
  // 归还资源
  synchronized void free(
    Object from, Object to){
    als.remove(from);
    als.remove(to);
    notifyAll();
  }
}
  1. 六项Happen-before原则
    A happen-before B,表示A操作对于B内存可见
    1. 顺序性规则
      单线程中,前面操作对后面操作可见,特别注意,不是表面上的代码顺序,是根据内存模型约束下编译器优化后的执行顺序
    2. volatile 变量规则
      一个 volatile 变量的写操作, Happens-Before于该变量的读操作
    3. 传递性
      A Happens-Before B,且B Happens-Before C,则A Happens-Before C
class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      // 这里 x 会是多少呢?
    }
  }
}
  1. 锁规则
    一个锁的解锁 Happens-Before 于后续对这个锁的加锁操作
  2. 线程 start() 规则
    线程A启动线程B之后,线程B能看到之前线程A做的所有操作
  3. 线程 join() 规则
    线程A等待线程B执行完成后,线程B的所有操作的对线程A可见
上一篇 下一篇

猜你喜欢

热点阅读