读懂Java并发系列——2.并发的特性

242 阅读2分钟

从本节开始探究并发原理,并发原理的了解是后续使用JDK并发工具以及自己编写并发场景代码的知识储备。 让我们先考虑一下两个问题

  1. 哪些场景需要考虑并发?
  2. 没有共享就没有并发问题之局部变量为什么不需要考虑并发?
  3. 并发中强调的原子性、有序性和可见性分别代表着什么?
  4. 分布式锁的存在,那Java本身的并发还是否有必要?

1. 并发闲谈

在本节将遗留以下问题让读者思考

1.1 什么情况下可以考虑并发?

并发是为了解决共享数据的问题,比如静态变量、数据库资源、redis数据,所以当在多个线程中有共享数据时就需要考虑并发问题。

1.2 我的程序有多少个线程?

1.3 我写的方法是否会存在并发问题?

1.4 共享——并发问题的祸源

2. 并发的特性

2.1 原子性

即一个操作是一个整体,不会存在执行一部分的情况。 例:i++操作不是原子性的 可以使用AtomicIntegerincrementAndGet()方法 或者使用Unsafe类的CAS操作。

2.2 有序性

出于提高性能的目的,JVM和内存会对指令进行重排序。

public class Singleton {
  private static Singleton instance;
  public static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}

image.png

为了防止指令重排序,可以使用synchronized或者lock机制

public class Singleton {
  private static Singleton instance;
  public synchronized static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}

2.3 可见性

可见性指在一个线程中修改的变量,能不能立刻被其它线程看到。 多线程环境中可见性问题主要由于工作内存的存在 内存分为两个部分:

image.png

每个线程都在使用工作内存,只有当工作内存失效时才会到主内存中读取数据。

// 演示程序:flag被修改为true,但是main线程中未及时得知,所以永远执行
public class Test {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true) {
            if (td.isFlag()) {
                System.out.println("---end break---");
                break;
            }
        }
    }
}

class ThreadDemo implements Runnable {
    private  boolean flag =false;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } flag = true;
        System.out.println("flag=" + flag);
    }

    public boolean isFlag() {
        return flag;
    }
}

要解决可见性问题,可以采用volatile关键字修饰,也可以让工作内存失效。 工作内存在何时失效?

  1. CPU空闲时(IO操作,System.out.xxx操作)
  2. 线程中释放锁时(如调用wait方法)
  3. 线程切换时(sleep()方法等)