大纲
JUC 介绍
JDK 5 中增加了并发大师 Doug Lea 的并发库,这一引进给 Java 线程的管理和使用提供了极大的便利性。在 Java 5 提供的 java.util.concurrent
(简称 JUC)包中,增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。JUC 提供了可调的、灵活的线程池,还提供了设计用于多线程上下文中的 Collection(即线程安全的集合类)实现等。常用的 JUC 包有以下几个:
- java.util.concurrent
- java.util.concurrent.locks
- java.util.concurrent.atomic
内存可见性
什么是内存可见性问题
内存可见性问题是指:当多个线程操作共享数据时,彼此不可见。
内存可见性问题的产生
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class ThreadDemo implements Runnable {
private boolean flag = false;
public boolean isFlag() { return flag; }
@Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; System.out.println(Thread.currentThread().getName() + " flag = " + flag); }
}
public class VolatileTest {
public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo, "T1").start();
while (true) { if (threadDemo.isFlag()) { System.out.println(Thread.currentThread().getName() + " flag = " + threadDemo.isFlag()); break; } } }
}
|
上述代码输出 T1 flag = true
后,主线程会一直卡死在 while
循环的执行中,因为主线程感知不到 T1 线程对共享变量 flag
的更改。
内存可见性问题的解决
volatile 的三大特性
volatile
是 Java 虚拟机提供的轻量级的同步机制,用来确保将变量的更新操作通知到其他线程。通常可以将 volatile
看做一个轻量级的锁,但是又与锁有些不同:
- 对于多个线程,不是一种互斥关系
- 不能保证变量状态的 “原子性操作”
volatile
拥有三大特性:
volatile 解决内存可见性问题
在多线程环境下,使用 volatile
关键字,可以保证内存可见性和禁止指令重排。特别注意,volatile 不能保证原子性,即多个线程同时操作共享数据时,存在线程安全问题,最终导致数据不一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class ThreadDemo implements Runnable {
private volatile boolean flag = false;
public boolean isFlag() { return flag; }
@Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; System.out.println(Thread.currentThread().getName() + " flag = " + flag); }
}
public class VolatileTest {
public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo, "T1").start();
while (true) { if (threadDemo.isFlag()) { System.out.println(Thread.currentThread().getName() + " flag = " + threadDemo.isFlag()); break; } } }
}
|
程序执行输出的结果:
1 2
| T1 flag = true main flag = true
|
synchonrized 解决内存可见性问题
synchronized
关键字的作用是让线程在进入方法或者同步代码块时自动获取锁,在退出时自动释放锁。当一个线程获取了锁之后,它会清空工作内存并从主内存中重新读取共享变量的值,这确保了线程读取到的变量值是最新的。当线程释放锁时,会将其在工作内存中对共享变量的修改刷写回主内存,以使其他线程可见。所以,使用 synchronized
也可以解决内存可见性问题,其底层是通过锁机制确保线程之间的同步和内存可见性。特别注意,由于 synchronized
是独占锁,在高并发场景下的性能较低,因此建议使用 volatile
来解决内存可见性问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class ThreadDemo implements Runnable {
private boolean flag = false;
public boolean isFlag() { return flag; }
@Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; System.out.println(Thread.currentThread().getName() + " flag = " + flag); }
}
public class VolatileTest {
public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo, "T1").start();
while (true) { synchronized (threadDemo) { if (threadDemo.isFlag()) { System.out.println(Thread.currentThread().getName() + " flag = " + threadDemo.isFlag()); break; } } } }
}
|
程序执行输出的结果:
1 2
| T1 flag = true main flag = true
|
CAS 算法