从本节开始探究并发原理,并发原理的了解是后续使用JDK并发工具以及自己编写并发场景代码的知识储备。 让我们先考虑一下两个问题
- 哪些场景需要考虑并发?
- 没有共享就没有并发问题之局部变量为什么不需要考虑并发?
- 并发中强调的原子性、有序性和可见性分别代表着什么?
- 分布式锁的存在,那Java本身的并发还是否有必要?
1. 并发闲谈
在本节将遗留以下问题让读者思考
1.1 什么情况下可以考虑并发?
并发是为了解决共享数据的问题,比如静态变量、数据库资源、redis数据,所以当在多个线程中有共享数据时就需要考虑并发问题。
1.2 我的程序有多少个线程?
1.3 我写的方法是否会存在并发问题?
1.4 共享——并发问题的祸源
2. 并发的特性
2.1 原子性
即一个操作是一个整体,不会存在执行一部分的情况。 例:i++操作不是原子性的 可以使用AtomicInteger的incrementAndGet()方法 或者使用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;
}
}
为了防止指令重排序,可以使用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 可见性
可见性指在一个线程中修改的变量,能不能立刻被其它线程看到。 多线程环境中可见性问题主要由于工作内存的存在 内存分为两个部分:
每个线程都在使用工作内存,只有当工作内存失效时才会到主内存中读取数据。
// 演示程序: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关键字修饰,也可以让工作内存失效。 工作内存在何时失效?
- CPU空闲时(IO操作,System.out.xxx操作)
- 线程中释放锁时(如调用wait方法)
- 线程切换时(sleep()方法等)