1、前言
这篇博客是对 Java 并发包(java.util.concurrent
)以及多线程的一些总结,记录一些常用用法和个人理解。由于目前还没有接触过真正的并发场景且,而且还缺少一些内容的铺垫,所以仅仅一些浅层面的使用,有问题的地方还请多多指正!
1.1 记录规划
JUC 包下的内容相对来说还是很多的,提供了各种各样的对并发编程的支持,这篇博客里要记录的大致有四类:
atomic
包里是一些原子类。locks
包里是一些锁。- 一些辅助类,比如
CountDownLatch
、CycliBarrier
、Semaphore
。 - 一些并发集合。
2、多线程下的问题
2.1 多线程回顾
线程状态
常用方法
Thread 类 | |
---|---|
Thread.yield() | 静态方法。暂停当前正在执行的线程对象,并执行其他线程 |
Thread.sleep() | 静态方法。让当前正在执行的线程休眠(暂停执行) |
Thread.currentThread().getName() | 静态方法。返回对当前正在执行的线程名 |
thread.start() | 使该线程开始执行;JAVA虚拟机调用该线程的run() 方法 |
thread.join(millis) | 等待该线程终止的时间最长为 millis 毫秒 |
Object 类 | WARNING:需要在同步方法或同步代码块中调用 |
---|---|
o.wait() | 使当前线程等待,直到另一个线程调用notify() 或notifyAll() |
o.notify() | 唤醒正在此对象的监视器上等待的单个线程 |
o.notifyAll() | 唤醒此对象的监视器上正在等待的所有线程 |
2.2 Java 内存模型(JMM)
简介
QUOTE
Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM),用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。
内容
QUOTE
JMM规定了所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。
2.3 误区
JMM 本身只是一层 Java 虚拟机(JVM)的规范,是由具体的 JVM 实现的。目前对 JVM 还不了解也不清楚具体的实现。但是对于 JMM 的理解上的一些误区却可能存在。
WARNING
比如“工作内存”和“主内存”这两个描述,所以引入知乎的一条帖子:
2.4 概扩
- 多线程之间的通信方式:共享内存、消息传递。
- 大多数情况下都需要以共享内存来进行多线程之间的通信(暂时只讨论这种方式)。
- 具体方式:多线程操作同一个共享变量(线程操作资源类)。
- 保证线程安全的指标:原子性、可见性、有序性。
- 保证线程安全的具体方式:加锁同步。
2.5 并发编程特性
- 原子性:操作中途不应被其他线程干扰,对参与不变性约束的变量的操作要么都成功,要么都不成功。
- 可见性:某个线程修改了共享变量,能够立即让其他线程知晓。
- 有序性:为了保证多线程并行的串行语义,需要禁止指令重排。
QUOTE
指令重排:编译器和 CPU 在满足数据依赖性的前提下会进行一些指令重排来提高效率。
3、volatile
3.1 可见性问题
如下代码中,main 线程对 sharedVar
的修改, t1 线程不能及时可见,故 t1 线程会一直死循环。
public class Test {
private static int sharedVar = 0;
public static void main(String[] args) {
new Thread(() -> {
while (sharedVar == 0) { }
System.out.println(sharedVar);
}, "t1").start();
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
sharedVar = 1;
}
}
使用 volatile
关键字修饰共享变量sharedVar
后,t1 线程及时可见 sharedVar
的修改,会跳出循环。
4、 synchronized
4.1 原子性问题
如下代码中,add()
方法里的 ++ 操作并不是一个原子性操作,10 条线程各加 1000 次结果并不能达到 10000。
public class Test {
private static int sharedVar = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(sharedVar);
}
private static void add() {
sharedVar++;
}
}
使用synchronized
同步add()
方法后,保证了代码块的原子性,结果可以加到 10000。
5、辅助工具
5.1 CoutDownLatch(减法计数器)
团灭了才能越泉:
public class Test {
public static void main(String[] args) throws InterruptedException {
int total = 5;
CountDownLatch countDownLatch = new CountDownLatch(total);
for (int i = 1; i <= total; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " has been slayed!");
countDownLatch.countDown();
}, "t" + i).start();
}
countDownLatch.await();
System.out.println("execute!");
}
}
5.2 CycliBarrier(加法计数器)
人齐了才能开团:
public class Test {
public static void main(String[] args) {
int total = 5;
CyclicBarrier cyclicBarrier = new CyclicBarrier(total, () -> {
System.out.println("发起进攻!");
});
for (int i = 1; i <= total; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "大招已经好了!");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, "t" + i).start();
}
}
}
5.3 Semaphone(信号量)
只有 4 个 buff:
public class SemaphoreDemo {
public static void main(String[] args) {
int total = 10;
int bufTotal = 2;
Semaphore semaphore = new Semaphore(bufTotal);
for (int i = 1; i <= total; i++) {
new Thread(() -> {
try {
// acquire 得到
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " got buff");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " lost buff");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// release 释放
semaphore.release();
}
}, "t" + i).start();
}
}
}
6、原子类
3.1 原子整型
public class Test {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}, "t" + i).start();
}
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(atomicInteger.get());
}
private static void add() {
atomicInteger.incrementAndGet();
}
}
7、并发集合
多线程下使用普通的集合可能会造成并发修改异常(ConcurrentModificationException),需要使用一些同步的集合。
7.1 CopyOnWriteArrayList
public class Test {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
}).start();
}
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
list.forEach(System.out::println);
}
}
7.2 ConcurrentHashMap
public class Test {
public static void main(String[] args) {
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 10; i++) {
final int temp = i;
new Thread(() -> {
map.put(String.valueOf(temp), UUID.randomUUID().toString());
}).start();
}
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(map);
}
}
8、锁
8.1 从“问题——解决方案”看各种锁
QUOTE 《java中的各种锁详细介绍》
8.2 ReentrantLock
使用 ReentrantLock 和 Condition 实现线程的精准通知唤醒:
-
三个线程分别循环任务 10 次,都按照 ta、tb、tc 的顺序执行。
-
ta 线程精准通知唤醒 tb 线程,tb 线程唤醒 tc 线程,tc 线程 通知唤醒 ta 线程。
资源类
class Data {
private int var = 1;
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
private Condition conditionC = lock.newCondition();
public void printA() {
lock.lock();
try {
// 等待
while (var != 1) { conditionA.await(); }
System.out.println(Thread.currentThread().getName() + " var=" + var);
var = 2;
// 唤醒
conditionB.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (var != 2) { conditionB.await(); }
System.out.println(Thread.currentThread().getName() + " var=" + var);
var = 3;
conditionC.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (var != 3) { conditionC.await(); }
System.out.println(Thread.currentThread().getName() + " var=" + var);
var = 1;
conditionA.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
main
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) { data.printA(); }
}, "ta").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) { data.printB(); }
}, "tb").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) { data.printC(); }
}, "tc").start();
}
}
9、待更新
- 指令
- 同步算法
- 阻塞队列
- 线程池