JAVA多线程高并发基础知识点
本文中的代码java版本:jdk11.
1. 线程基础
1.1. 线程概念
线程是操作系统能够进行运算调度的最小单位(程序执行流的最小单元)。
它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
1.2. 线程安全
- 原子性:线程共享内存同一时间只有一个线程可以操作(synchronized关键字,互斥锁)。
- 可见性:某个线程的操作可以立即被其他线程观察到(volatile关键字,强制刷新线程内该变量内存)。
- 有序性:线程内指令顺序执行(现代CPU指令重排,乱序执行);在java中指令重排不影响单线程内的最终结果,但不保证多线程。
1.3. 创建并启动线程的方法
- 实现 Runnable 接口
class ByRunnable implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable并实现run方法");
}
}
Thread thread = new Thread(new ByRunnable());
thread.start();//调用start以开启新线程
thread.join();//阻止主线程在新线程之前结束
- 继承 Thread 类
class ByThread extends Thread{
@Override
public void run() {
System.out.println("继承Thread并重写run方法");
}
}
Thread thread = new ByThread();
thread.start();
thread.join();
- 守护线程
Thread thread = new Thread(()->System.out.println("lambda thread"));
//将线程设置为守护线程
//JVM会在所有非守护线程结束后自动退出,
//守护线程不会随着JVM退出而结束,
//因此守护线程如果持有需要关闭的资源,可能会因无法正常关闭资源(JVM退出)而造成严重后果,
//如打开文件可能会导致数据丢失.
thread.setDaemon(true);
thread.start();
1.4. 线程状态
使用Thread.currentThread().getState();
可以获得当前线程状态,thread.getState();
获得某一线程的状态。
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
- 阻塞(BLOCKED):表示线程阻塞于锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
- 终止(TERMINATED):表示该线程已经执行完毕。
1.5. 线程超时等待(TIMED_WAITING)相关方法
-
线程睡眠(sleep),当持有锁时不释放锁!且sleep的线程无法从外部唤醒,但可被中断!
try { //睡眠1000毫秒,在此期间不参与CPU资源竞争(线程调度) Thread.sleep(1000); } catch (InterruptedException e) { //可能在睡眠结束前线程就被中断了 e.printStackTrace(); }
-
线程让出(yield)
//给CPU一个提示,让出本次线程调度,但参与下一次调度(进入线程等待队列),CPU可能会忽略本次让出 Thread.yield();
-
线程加入(join)
Thread t = new Thread(()->{ System.out.println("new Thread....."); }); t.start(); //将线程t加入当前线程,即告诉当前线程等待线程t结束或在1000毫秒后再继续 t.join(1000);
-
线程超时等待(wait),只有持有锁的对象才能调用这个方法且会释放锁!
//使获得锁的obj对象所在线程进入等待,等待1000毫秒后自动被唤醒,或者被notify(notifyAll)唤醒 obj.wait(1000);
-
线程超时禁用(park)
//等待到当前时间的1000毫秒结束禁用 LockSupport.parkUntil(System.currentTimeMillis()+1000); //禁用当前线程10000纳秒 LockSupport.parkNanos(10000);
1.6. 线程等待(WAITING)相关方法
-
wait、notify、notifyAll:wait使当前获得锁的线程等待并释放锁,notify唤醒随机一个等待的线程来竞争锁及notifyAll唤醒所有等待的线程来竞争锁,但notify操作不释放锁(只有持有锁的对象才能调用这些方法,且notify和notifyAll只能唤醒在同一个锁对象下wait的线程)
//模拟仓库 ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>(); //模拟生产者 Thread producer = new Thread(()->{ for(int i=0;;++i){ try { while (queue.size() < 5) { Thread.sleep(100); queue.add("prod"+(++i)); } } catch (InterruptedException e) { e.printStackTrace(); } synchronized (queue) { //随机唤醒一个等待中的线程 queue.notify(); if(queue.size() >= 5) { try { //仓库满了使当前线程等待 queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }); //模拟消费者 Thread consumer = new Thread(()->{ for(;;){ try { while (queue.size() > 0 ) { Thread.sleep(10); System.out.println(queue.poll()); } } catch (InterruptedException e){ e.printStackTrace(); } synchronized (queue) { //唤醒所有其他线程,此时最多只有一个线程(生产者)在等待 queue.notifyAll(); if(queue.size()<=0) { try { //商品消费完了,使当前线程等待 queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }); producer.start(); consumer.start(); producer.join(); consumer.join();
-
park和unpark:park使线程等待及unpark使线程结束等待,park时当持有锁时不释放锁!可以在外部被unpark唤醒!
Thread t = new Thread(()->{ System.out.println("new....."); LockSupport.park();//让线程进入等待 System.out.println("over wait"); }); t.start(); Thread.sleep(1000); System.out.println("main sleep 1000"); LockSupport.unpark(t);//唤醒指定线程t /*控制台输出: new..... main sleep 1000 over wait */
2. synchronized关键字
- synchronized是同步、互斥、独占、可重入的锁
- synchronized锁住的是对象,当没有传入对象时:
- 当对静态方法加锁时锁住的是当前类的class对象。
- 当对实例方法枷锁时锁住的时当前实例对象(this)。
- 在方法内使用synchronized时必须传入对象
synchronized(obj){/*sync code*/}
并锁住其代码块。
- synchronized锁住的对象才可以使用wait、notify、notifyAll。
- synchronized存在锁升级的概念
- 当始终同时只有同一个线程竞争时,锁时处于偏向锁状态(markword只记录线程标识而不尝试加锁且不主动释放记录的线程标识,此时默认不会有其他线程竞争锁)。
- 在获得锁的线程未结束时有线程来竞争锁,锁升级为轻量级锁(自旋锁,线程不调用系统函数进入CPU等待队列)。
- 当竞争变大(10次)或者锁持续时间变长时,锁升级为重量级锁(调用系统函数使线程进入CPU等待队列)。
3. volatile关键字
-
保证线程可见性
- 在java中有共享内存,也有线程内工作内存,当持有共享内存的值时先将共享内存中的值复制到工作内存,每次修改值时先修改工作内存,写回到共享内存的时机由CPU控制(线程之间不可见),可能出现A线程改变某一个线程共享变量的值后B线程无法及时看到更新后的值.当使用volatile关键字后,每次修改该变量都会及时回写到共享内存并通知其他线程.
- 保证线程可见性的缓存一致性协议:Intel:MESI.
-
禁止指令重排序
-
指令不会被CPU和编译器优化乱序执行,而是按照程序流线性执行.
-
volatile关键字在Double Check Lock(DCL)单例的应用 :
public class Test { private static volatile Test INSTANCE; public int i; private Test(){i=100;} //懒汉单例 public static final Test instance(){ if (INSTANCE==null) { synchronized (Test.class) { //Double Check 双重检测 if (INSTANCE==null) { //new一个对象分三步: //1.申请内存(对象成员变量赋初值); //2.初始化成员变量(按照代码要求赋值); //3.将new对象赋值给变量. //new的过程不是原子的,可以指令重排序. //初始化单例时,volatile保证不会乱序执行,其他线程不会得到未完全初始化的单例. //即如果指令重排序了,先赋值了对象变量,再初始化对象内成员变量, //另一个线程得到的单例可能是未完全初始化的,对象的变量i可能是初值0 INSTANCE = new Test(); } } } return INSTANCE; } }
-
volatile不可避免的会降低程序执行效率.
4. Atomic原子操作类及CAS
4.1. CAS
全称: Compare And Swap (比较并交换) 或者说 Compare And Set.
以CAS做底层的锁是一种轻量级乐观锁,当前线程不会进入等待队列.默认很快就会得到锁.
CAS是一种无锁算法(不与内核通信,不放弃CPU,循环执行比较直到预期值,完成变量赋值).
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B. 当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做.
AtomicInteger::incrementAndGet底层的CAS
//代码位于Unsafe.class
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
@HotSpotIntrinsicCandidate
public final boolean weakCompareAndSetInt(Object o, long offset,
int expected,
int x) {
return compareAndSetInt(o, offset, expected, x);
}
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
CAS伪代码实现
cas(value,expected,newValue){
if(value == expected) value = newValue;
else{
//try again or fail
}
}
CAS中的ABA问题
A线程对变量I的预期值为0,由于从内存中获取I需要时间,此时其他线程先将变量I改为其他值后又改会0,虽然此时依然满足A线程的预期值,但不是原来的预期值了,此时就发生了ABA问题,如果此时的偷梁换柱影响业务,一般的解决方式是加版本号.
4.2. 原子类
java原生提供了原子操作的对象,以用于线程间共享内存,atom类型在操作内存时主要使用了CAS模式.
通常情况下修改内存需要的时间都非常短暂,因此大多数情况下,在多个线程操作同一个变量时,原类型操作内存(CAS)的效率要高于使用同步代码块(synchronized)的效率
AtomicReference
包裹一个类型,使该变量的操作具有原子性,主要用于线程间共用变量
AtomicReference<Integer> ar = new AtomicReference<>();
new Thread(()->{
ar.set(1234);
}).start();
new Thread(()->{
System.out.println(ar.get());
}).start();
AtomicInteger
原子int类型,内部对数据的操作利用CAS模式
AtomicInteger ai = new AtomicInteger();
ai.incrementAndGet();
LongAdder
jdk1.8提供的用于超高并发情况下的Long类型计数器,以代替AtomicInteger或AtomicLong,在高并发下计算效率最高,并提供了更方便的操作方法.
LongAdder比AtomicLong更快的原因是采用了分段思想,使用了一个非volatile的Cell数组来存储累积线程作为分段锁,当Cell数组不为空时,
进行两次CAS,第一次CAS当前操作的累积的Cell数组中的某个元素,第二次再CAS被volatile修饰的long值,
避免了直接修改long值时,许多线程CAS竞争时频繁空转,加快了CPU效率
LongAdder a = new LongAdder();
a.add(2);
int i = a.intValue();//返回当前的总和值并截断为int类型,返回的结果是原子操作后的
System.out.println(i);
a.increment();
System.out.println(a.intValue());
long l = a.sum();//当前的总和值,返回的结果是非原子的
System.out.println(l);
LongAdder::add
/**
* Adds the given value.
* @param x the value to add
*/
public void add(long x) {
Cell[] cs; long b, v; int m; Cell c;
if ((cs = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (cs == null || (m = cs.length - 1) < 0 ||
(c = cs[getProbe() & m]) == null ||
//c.cas:第一次CAS,这个c是cs数组中的一个元素,cs是一个分段锁
!(uncontended = c.cas(v = c.value, v + x)))
//这里面会用CAS实际add真正的long值
longAccumulate(x, null, uncontended);
}
}
其他
所有java原生提供的原子类型都是在java.util.concurrent.atomic
包下提供的
4.3. Unsafe类
获取unSafe实例
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe)theUnsafe.get(Unsafe.class);
利用Unsafe直接操作内存
long l = unsafe.allocateMemory(8);//分配内存
unsafe.setMemory(l,0L,(byte)0);//操作内存
unsafe.freeMemory(l);//释放内存
利用Unsafe直接生成并操作对象
class My{
int a = 111;
}
//直接生成实例
My o = (My) unsafe.allocateInstance(My.class);
long offset = unsafe.objectFieldOffset(My.class.getDeclaredField("a"));
//直接通过偏移量操作对象属性
unsafe.getAndSetInt(o,offset,1234);
System.out.println(o.a);//输出 1234
利用Unsafe操作CAS
unsafe.compareAndSwapInt(obj,offset,intVal);
unsafe.compareAndSwapLong(obj,offset,longVal);
5. 各种JUC包下的同步锁
5.0 死锁
死锁的四个必要条件:
- 互斥:资源唯一,且线程互斥;
- 不可剥夺:已获得资源后不可剥夺资源,只能主动释放;
- 请求与保持:已获得资源的线程尝试获得其他唯一资源,而该资源已被其他线程占有;
- 循环等待:存在一种资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求.
避免死锁的核心思想: 打破死锁的四个必要条件
- 加锁顺序
- 加锁时限
- 死锁检测
避免死锁的常用方法:
- 有序资源分配法
- 银行家算法
解除死锁的常用方法:
- 设置线程还原点
- 撤销线程
- 剥夺资源
5.1. ReentrantLock
ReentrantLock是可重入锁,且需要手动申请锁,手动释放锁
尝试申请锁,申请不到不阻塞线程而是以无锁继续执行,返回true表示申请到锁: ReentrantLock::tryLock
阻塞申请锁,未申请到时将阻塞线程:ReentrantLock::lock
阻塞申请可以被打断的锁:ReentrantLock::lockInterruptibly
,打断锁:Thread::interrupt
,这种方式申请的锁可中断线程解除死锁(捕捉中断异常释放或在finally块中释放)
释放已经申请的锁:ReentrantLock::unlock
公平锁与非公平锁的区别:
- 非公平锁:执行lock时直接争抢锁,
synchronized
关键字是非公平锁。 - 公平锁:执行lock时,先检查锁等待队列,如果队列不为空,则加入锁队列,否则直接争抢锁。
package test;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest {
private static final String thStr(){
return Thread.currentThread().getId()+" "+Thread.currentThread().getName();
}
public static class ReentrantLockTest {
private final static Lock lock1 = new ReentrantLock();//入参为true为公平锁,等待锁最久将获得锁
private final static Lock lock2 = new ReentrantLock();
private static final void doSomething() {
String str = thStr();
try {
System.out.println(str);
lock1.lock();//获得锁
System.out.println(str+" alloc lock");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(str+" will free lock");
lock1.unlock();//释放锁一定要在finally块中,以保证得到执行
}
}
//常规获得锁
public final static void testLock() {
for (int i=0;i<10;i++){
new Thread(()->{
doSomething();
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//死锁及解除死锁
public static final void deadLock() {
//两个线程相互获取对方锁
var t1 = new Thread(new DeadLockThread(lock1,lock2));
var t2 = new Thread(new DeadLockThread(lock2,lock1));
t1.start();
t2.start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();//中断线程1,使线程2获得锁(解除死锁)
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static final class DeadLockThread implements Runnable {
private Lock lock1,lock2;
public DeadLockThread(Lock lock1, Lock lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
String str = thStr();
try {
lock1.lockInterruptibly();//获得可被打断的锁(以实现可解除死锁)
System.out.println(str+" acquire lock1");
TimeUnit.MILLISECONDS.sleep(100);
lock2.lockInterruptibly();
System.out.println(str+" acquire lock2");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(str+" will free lock1");
lock1.unlock();
System.out.println(str+" will free lock2");
lock2.unlock();
}
}
}
//超时休眠重新尝试获取锁
public static final void tryLockTest(){
ArrayList<Thread> threads = new ArrayList<>(8);
for(int i=0;i<4;i++){
threads.add(new Thread(()->{
tryLock();
}));
}
threads.forEach(thread -> thread.start());
threads.forEach(t-> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
private static final void tryLock(){
String str = thStr();
try {
while (!lock1.tryLock(1,TimeUnit.SECONDS)){
System.out.println(str + " try acquire lock1 filed");
Thread.sleep(300);
}
System.out.println(str + " acquire lock1");
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(str+" attempt free lock1");
lock1.unlock();
}
}
//Condition等待和唤醒
public static final void testCondition(){
ArrayList<Thread> threads = new ArrayList<>();
for (int i=0;i<20;i++) {
if(i%2==0){
threads.add(new Thread(()->ConditionTest.get()));
}else{
threads.add(new Thread(()->ConditionTest.put()));
}
}
threads.forEach(t->t.start());
}
private static final class ConditionTest{
private static final Condition condP = lock1.newCondition();
private static final Condition condG = lock1.newCondition();
private static final ArrayList<String> list = new ArrayList<>();
private static final AtomicInteger ai = new AtomicInteger(0);
public static final void put(){
String str = thStr();
try {
lock1.lock();
System.out.println(str+" try put");
if(list.size()>=3){
System.out.println(str+" list size >=3 will await");
condP.await(300,TimeUnit.MILLISECONDS);//线程进入等待,同时释放锁
}
list.add("item "+ai.incrementAndGet());
if(list.size()>=1){
System.out.println(str+" now list size "+list.size());
condG.signalAll();//唤醒等待的锁
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(str+" unlock put");
lock1.unlock();
}
}
public static final void get(){
String str = thStr();
try {
lock1.lock();
System.out.println(str+" try get");
if(list.size()<=0){
System.out.println(str+" list size <=0 will await");
condG.await();
}
try {
System.out.println(str+" "+list.remove(0));
} catch (RuntimeException e){
System.err.println(str+" list size is zero, will retry");
get();
}
if(list.size()<3)
condP.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(str+" unlock get");
lock1.unlock();
}
}
}
}
}
5.2. ReadWriteLock 和 StampedLock
读写锁同时实现了共享锁(读)和排他锁(写).
读锁对所有读线程共享,写锁对所有操作线程排他.
为满足强一致性,ReadWriteLock
读锁采用悲观锁,即如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁.
读写锁可能造成活锁(某些线程一直拿不到锁).
读锁可以避免读到写入一半的数据,写锁避免写入混乱.
在写少读多的场景下ReadWriteLock
性能会明显高于ReentrantLock
.
StampedLock
是ReadWriteLock
的改进版,主要改进了读锁为乐观锁,提高了并发性能,但代价是可能读取到中间数据,需要业务自己判断取舍.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Test {
public static void main(String[] args) throws Exception {
threadTest();
}
static void threadTest() throws Exception {
var l = new ReentrantReadWriteLock();
var rl = l.readLock();
var wl = l.writeLock();
var ts = new Thread[9];
for(var i=0;i<9;++i){
final var iF = i;
ts[i] = new Thread(()->{
for(var j=0;j<2;++j){
//写用写锁
if(iF/4==0 && j==1)write(wl,iF+j);
//读用读锁
else read(rl);
}
});
}
for (Thread t : ts) {
t.start();
t.join();
}
}
private static int i;
private static void read(Lock l){
try {
l.lock();
Thread.sleep(100);
System.out.println("read "+i);
} catch (Exception e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
private static void write(Lock l, int v){
try {
l.lock();
Thread.sleep(50);
System.out.println("write "+v);
i = v;
} catch (Exception e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
}
/*控制台输出:
read 0
write 1
read 1
write 2
read 2
write 3
read 3
write 4
read 4
read 4
read 4
read 4
read 4
read 4
read 4
read 4
read 4
read 4
*/
import java.util.concurrent.locks.StampedLock;
public class Test {
public static void main(String[] args) throws Exception {
threadTest();
}
static void threadTest() throws Exception {
var l = new StampedLock();
var ts = new Thread[9];
for(var i=0;i<9;++i){
final var iF = i;
ts[i] = new Thread(()->{
for(var j=0;j<2;++j){
if(iF%4==0)write(l,iF+j);
else read(l);
}
});
}
for (Thread t : ts) {
t.start();
t.join();
}
}
private static int i;
private static void read(StampedLock l){
var stamp = l.tryOptimisticRead();
// 检查读锁后是否有其他写锁发生
if(l.validate(stamp)){
try {
//乐观读锁
stamp = l.readLock();
Thread.sleep(100);
System.out.println("read "+i);
} catch (Exception e) {
e.printStackTrace();
} finally {
l.unlockRead(stamp);
}
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("has write");
}
}
private static void write(StampedLock l, int v){
//写锁
var stamp = l.writeLock();
try {
Thread.sleep(50);
System.out.println("write "+v);
i = v;
} catch (Exception e) {
e.printStackTrace();
} finally {
l.unlockWrite(stamp);
}
}
}
5.3. CountDownLatch
一个类似于门闩的同步工具类,可以阻塞当前线程,直到计数归零或到达超时时间。
CountDownLatch只能countDown,这意味着只能阻塞一次。
如果在调用CountDownLatch::await
时计数已经归零则不会阻塞当前线程,否则阻塞当前线程直到计数归零。
使用CountDownLatch::countDown
使计数减一。
一个线程可以调用多次countDown
,是计数多次减一。
主要用来开启多个线程处理任务,并在全部处理结束后通知主线程处理接下来的任务。
//创建一个从2开始计数的CountDownLatch
CountDownLatch cd = new CountDownLatch(2);
for(int i=0;i<2;++i){
new Thread(()->{
try {
Thread.sleep(1000);
}catch (Exception e){}
finally {
//当线程完成自己的任务后cd计数减一
cd.countDown();
}
}).start();
}
//阻塞当前线程直到cd计数归零
cd.await();
System.out.println("main");
5.4. CyclicBarrier
一个类似于栅栏的同步工具类,当计数器到达上限时唤醒所有阻塞的线程(放行)并可以执行指定任务。
CyclicBarrier可以循环使用,当到达临界值时放行后自动归零并重用。
CyclicBarrier可以用来做同步(最后一个线程调用await
后才能放行全部线程)。
调用CyclicBarrier::await
使线程等待,直到等待的线程到达CyclicBarrier的临界值,所有await的线程将被唤醒。
由于使用await
相较于CountDownLatch
来说一个线程在一次"推倒栅栏"之前只能使计数器加一一次。
//创建一个临界值为3的CyclicBarrier对象,并在计数到达3时执行指定任务,这个任务将先于被放行的线程执行
CyclicBarrier cb = new CyclicBarrier(3, ()->{
//这段代码将使用最后一个执行await的线程执行,然后所有线程将执行其后的操作
System.out.println(Thread.currentThread().getName()+" ========== we go~ time:"+new Date().getTime());
});
for(int i=0;i<3;++i){
int iF= i;
new Thread(()->{
try {
Thread.sleep(iF*1000);
System.out.println(Thread.currentThread().getName()+" "+iF+" wait time:"+new Date().getTime());
//等待栅栏打开,这个动作会使计数器加一,当加到临界值时全部等待的线程将开始执行
cb.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
} finally {
//cb.reset();//重置计数
//栅栏打开后将执行的任务,放行后栅栏重置,可以再次await
System.out.println(Thread.currentThread().getName()+" "+iF+" go time:"+new Date().getTime());
try {
Thread.sleep(4000-iF*1000);
System.out.println(Thread.currentThread().getName()+" "+"| "+iF+" wait2 time:"+new Date().getTime());
//再次等待栅栏打开
cb.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()+" "+"| "+iF+" go2 time:"+new Date().getTime());
}
}
}).start();
}
/*控制台输出:
Thread-0 0 wait time:1616147361601
Thread-1 1 wait time:1616147362604
Thread-2 2 wait time:1616147363613
Thread-2 ========== we go~ time:1616147363613
Thread-0 0 go time:1616147363623
Thread-1 1 go time:1616147363623
Thread-2 2 go time:1616147363623
Thread-2 | 2 wait2 time:1616147365631
Thread-1 | 1 wait2 time:1616147366631
Thread-0 | 0 wait2 time:1616147367633
Thread-0 ========== we go~ time:1616147367633
Thread-0 | 0 go2 time:1616147367633
Thread-2 | 2 go2 time:1616147367633
Thread-1 | 1 go2 time:1616147367633
*/
5.5. Phaser
一个阶段性同步工具类,当所有线程到达某一阶段时进入下一阶段,可以看作CyclicBarrier的升级版。
每当所有线程都执行了arrive
则进入下一阶段。直到所有线程都注销(deregister)了则流程结束。
每一个Phaser
都允许有父Phaser
,这样可以将多个Phaser
串联组成一个复杂的流程。
Phaser
可以用来做阶段性的流程控制。
import java.util.concurrent.*;
public class Test {
public static void main(String[] args) throws Exception {
threadTest();
}
/**执行测试*/
static void threadTest() throws Exception {
var wp = new WeddingPhaser();
Thread pst[] = new Thread[7];
for(var i=0;i<5;++i) pst[i]=new Thread(new Person(wp,"宾客" + i));
pst[5]=new Thread(new Person(wp,"新郎"));
pst[6]=new Thread(new Person(wp,"新娘"));
//所有线程都建立完成了才可以开始执行,否则流程将错乱(某些成员可能在第二阶段及其后才注册)
for (Thread t : pst) t.start();
}
/**创建自己的Phaser并指定某一阶段执行什么任务*/
static class WeddingPhaser extends Phaser {
//每一次到达阶段时将执行的任务,返回true时表示所有阶段完成
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase) {
case 0:
System.out.println("\n阶段"+phase+"所有人到齐,共"+registeredParties);
return false;
case 1:
System.out.println("\n阶段"+phase+"所有人用餐完毕,共"+registeredParties);
return false;
case 2:
System.out.println("\n阶段"+phase+"所有宾客离场,共"+registeredParties);
return false;
case 3:
System.out.println("\n阶段"+phase+"新郎新娘洞房,共"+registeredParties);
return true;
default:
return super.onAdvance(phase,registeredParties);
}
}
private void phase0(String name) throws Exception{
System.out.print(name+"到场 ");
Thread.sleep(1000);
this.arriveAndAwaitAdvance();
}
private void phase1(String name) throws Exception{
System.out.print(name+"用餐 ");
Thread.sleep(1000);
this.arriveAndAwaitAdvance();
}
private void phase2(String name) throws Exception{
if(name.startsWith("宾客")){
//所有宾客在此阶段离场
System.out.print(name+"离场 ");
Thread.sleep(1000);
//到达最后阶段,注销自己
this.arriveAndDeregister();
}else{
//新郎新娘送客但不离场
System.out.print(name+"送客 ");
Thread.sleep(1000);
this.arriveAndAwaitAdvance();
}
}
private void phase3(String name) throws Exception{
if(name.startsWith("新")){
System.out.print(name+"洞房 ");
Thread.sleep(1000);
}else{
//宾客在上一阶段就已经注销了自己,但由于是异步的,因此这个阶段可能还会有宾客线程未销毁,依然执行
System.out.print(name+"到家 ");
}
//阶段全部结束,注销自己
this.arriveAndDeregister();
}
public void runPhase(String name){
try {
phase0(name);
phase1(name);
phase2(name);
phase3(name);
} catch (Exception e){}
}
}
/**参与Phaser的成员*/
static class Person implements Runnable {
final WeddingPhaser wp;
final String name;
Person(WeddingPhaser wp,String name){
this.wp = wp;
this.name = name;
this.wp.register();
}
@Override
public void run() {
wp.runPhase(name);
}
}
}
/*控制台输出:
宾客1到场 新郎到场 宾客3到场 宾客4到场 宾客2到场 宾客0到场 新娘到场
阶段0所有人到齐,共7
新娘用餐 宾客3用餐 宾客1用餐 宾客0用餐 宾客4用餐 新郎用餐 宾客2用餐
阶段1所有人用餐完毕,共7
新郎送客 宾客0离场 新娘送客 宾客4离场 宾客1离场 宾客3离场 宾客2离场 宾客4到家 宾客0到家
阶段2所有宾客离场,共2
宾客3到家 新娘洞房 新郎洞房
阶段3新郎新娘洞房,共0
*/
5.6. Semaphore
一个可以反复使用的,限制最大活跃线程数量的便捷工具类,常用来做限流.
当信号量为0时不再有线程可以获取到信号量,直到有其他线程放弃信号量.
var s = new Semaphore(1);//同时只能活跃1个线程
for(var i=0;i<2;++i){
var iF = i;
var t = new Thread(()->{
for(var j=0;j<9;++j){
try {
s.acquire();//获得信号量,没获得信号量的线程将等待
Thread.sleep(100);
System.out.println("t"+iF+" is running");
} catch (Exception e) {e.printStackTrace();} finally {
s.release();//释放信号量
}
}
});
t.start();
System.out.println(t.getState());
}
5.7. Exchanger
简单易用的线程通信工具类,交换两个线程的数据,每当交换完成后就可以重用,如交易双方,一手交钱一手交货才完成交换.
var ec = new Exchanger<String>();
for(var i=0;i<2;++i){
var iF = i;
new Thread(()->{
String s = "t"+iF, s1 = "T"+iF;
try {
//交换数据,阻塞线程直到双方都调用了这个方法
s = ec.exchange(s);
System.out.println("t"+iF+" var:"+s);
s1 = ec.exchange(s1);
System.out.println("t"+iF+" var1:"+s1);
} catch (InterruptedException e) {e.printStackTrace();}
}).start();
}
Thread.sleep(100);
/*控制台输出:
t0 var:t1
t1 var:t0
t0 var1:T1
t1 var1:T0
*/
5.8. LockSupport
一个用来支撑自定义锁的工具类,也可以直接使用使当前线程等待
var t = new Thread(()->{
LockSupport.park();//使自己进入等待, 如果持有锁,不会自动释放锁
System.out.println("等待结束");
System.out.println(new Date().getTime());
});
t.start();
System.out.println(new Date().getTime());
Thread.sleep(1000);
//使线程t允许运行,
//如果unpark先于park那么该次park将会直接跳过,即该次park不会造成线程等待
LockSupport.unpark(t);
Thread.sleep(100);
/*控制台输出:
1616410337402
等待结束
1616410338411
*/
5.9. AbstractQueuedSynchronizer
AQS是锁的核心.
AQS的核心是一个volatile int类型的变量state,以及监控它的一个双向链表,链表的每个Node装的线程.
每当线程尝试获得锁时,先检查state是否为0,为0则争抢锁,大于0时如果当前线程已经获得锁则重入否则尝试获得锁失败,进入链表尾部(CAS)等待获得锁.
6. 并发容器
6.1. 阻塞队列
SynchronousQueue
//容量为0的阻塞队列,用于线程间交换数据,直接将数据送往另一个线程
public static SynchronousQueue synchronousQueue = new SynchronousQueue();
public static void main(String[] args) throws Exception{
new Thread(()->{
try {
//取出
System.out.println(synchronousQueue.take());
System.out.println(synchronousQueue.take());
System.out.println("OVER2");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
//阻塞等待索取
synchronousQueue.put("synchronousQueue2");
synchronousQueue.put("synchronousQueue1");
System.out.println("OVER1");
}
TransferQueue
//提供独特的transfer方法,阻塞当前线程,直到被transfer放入的数据被取出
public static TransferQueue transferQueue = new LinkedTransferQueue();
public static void main(String[] args) throws Exception{
for (int i = 0; i < 2; i++) {
new Thread(()->{
try {
//取出数据
System.out.println(transferQueue.take());
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
//阻塞到被取出
transferQueue.transfer("transferQueue1");
System.out.println("transferQueue1over");
transferQueue.transfer("transferQueue2");
System.out.println("transferQueue2over");
}
DelayQueue
//延时队列
static class MyDelayed implements Delayed{
String msg;
long time;
public MyDelayed(String msg, long time) {
this.msg = msg;
this.time = time+System.currentTimeMillis();
}
@Override //到达执行时间的剩余时间
public long getDelay(TimeUnit unit) {
return unit.convert(time-System.currentTimeMillis(),TimeUnit.MILLISECONDS);
}
@Override //确定优先级
public int compareTo(Delayed o) {
return (int)(getDelay(TimeUnit.MILLISECONDS)-o.getDelay(TimeUnit.MILLISECONDS));
}
}
public static DelayQueue<MyDelayed> delayQueue = new DelayQueue();
public static void main(String[] args) throws Exception{
delayQueue.add(new MyDelayed("asd3",2000l));
delayQueue.add(new MyDelayed("asd1",1000l));
delayQueue.add(new MyDelayed("asd2",1500l));
MyDelayed myDelayed;
while ((myDelayed=delayQueue.take())!=null)
System.out.println(myDelayed.msg+" ,current: "+System.currentTimeMillis());
}
ArrayBlockingQueue和LinkedBlockingQueue
用于在线程间传递任务
static void threadTest() throws Exception {
var q = new LinkedBlockingQueue<String>(4);
var t1 = new Thread(()->{
for(var i=0;i<100;++i){
try {
q.put("s"+i);
if(i==99)q.put("over");
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
var t2 = new Thread(()->{
for(;;){
try {
var s = q.poll();
System.out.println(s);
if(s!=null&&s.equals("over"))return;
Thread.sleep(51);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
6.2. 其他并发相关容器
HashTable和HashMap
Hashtable
是一个同步的Map
容器,它使用synchronized
关键字做同步,这个设计在高并发下性能低下,基本不再使用.
Hashtable
和HashMap
最大的区别是HashMap
完全不加锁.
通过Collections.synchronizedMap(new HashMap<>())
将HashMap
变为加锁的版本.
Vector和ArrayList
Vector
是一个List
容器,它使用synchronized
关键字做同步,这个设计在高并发下性能低下,基本不再使用.
Vector
和ArrayList
最大的区别是ArrayList
完全不加锁.
通过Collections.synchronizedList(new ArrayList<>())
将ArrayList
变为加锁的版本.
ConcurrentHashMap
高性能并发Map,内部采用了CAS
var map = new ConcurrentHashMap<String,String>();
map.put("a","A");
System.out.println(map.get("a"));
ConcurrentSkipListMap
高性能并发Map,内部采用了CAS,和ConcurrentHashMap
相比,ConcurrentSkipListMap
是有序的,且便利速度明显快于ConcurrentHashMap
var map = new ConcurrentSkipListMap<String,String>();
map.put("a","A");
System.out.println(map.get("a"));
ConcurrentLinkedQueue
高性能并发Queue,内部采用了CAS
var tickets = new ConcurrentLinkedQueue<String>();
for(var i=0;i<100;++i){
tickets.add("ticket"+i);
}
var ts = new Thread[3];
for(var i=0;i<3;++i){
ts[i] = new Thread(()->{
for(;;){
var tk = tickets.poll();//取出,如果使用ArrayList,下面则可能输出空
if(tk!=null) System.out.println(tk);
else return;
}
});
}
for (Thread t : ts) t.start();
for (Thread t : ts) t.join();
CopyOnWriteList
写时复制List.
读取数据时不加锁,写数据时加锁,先拷贝原内存到一个新的内存,并添加一个位置存储新的数据,写完后将指向原内存的引用指向新内存.
典型的以空间换时间.
在写少读多的场景下性能极高.
var l = new CopyOnWriteArrayList<String>();
l.add("asd");
System.out.println(l.get(0));
7. 线程并发
并发(concurrent)和并行(parallel)的关系:并发是指任务提交(不一定并行),并行是指任务执行,并行是并发的子集.
7.1. 线程池
ThreadPoolExecutor
一个持有一定量线程的池子,并发情况下避免频繁创建销毁线程,并可以提供定制化拒绝策略.
一般不使用默认线程池,他们各自有着缺陷.
常用的定时任务线程池quartz
.
//完整构造线程池
public ThreadPoolExecutor(int corePoolSize, //核心线程数(核心线程不会被销毁)
int maximumPoolSize, //最大线程数
long keepAliveTime, //超过核心线程数的线程的最大空闲生存时间,其后将可能被销毁
TimeUnit unit, //keepAliveTime的单位
BlockingQueue<Runnable> workQueue, //线程队列,当线程数超过核心线程数时入队
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler) //当线程数满,队列满时的拒绝策略
{
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
ThreadPoolExecutor::execute执行的三个步骤
//ThreadPoolExecutor::execute
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* 分三步进行:
* 1. 如果运行的线程少于corePoolSize,
* 尝试以command作为第一个task开启一个一个新核心线程.
* 2. 如果成功将command入队workQueue,
* 双重检测确保线程池正RUNNING,
* (可能有其他线程执行了shutdown).
* 如果线程池已经shutdown,则回滚入队操作,
* 并执行拒绝策略
* 3. 如果无法入队,直接添加新的工作线程并执行command,
* 如果操作失败了,则说明线程池可能已经shutdown或饱和了,
* 则执行拒绝策略
*/
//获取ctl快照
int c = ctl.get();
//第一步
//判断工作线程数是否少于设定的核心线程数值
if (workerCountOf(c) < corePoolSize) {
//添加核心工作线程
if (addWorker(command, true))
return;
//重新获取ctl快照(ctl可能已被其他线程修改)
c = ctl.get();
}
//第二部
//如果线程池正RUNNING,将command加入workQueue
if (isRunning(c) && workQueue.offer(command)) {
//重新获取ctl快照
int recheck = ctl.get();
//双重检测,确保线程池没有shutdown,如果shutdown了则将command出队workQueue
if (! isRunning(recheck) && remove(command))
//执行拒绝策略
reject(command);
//判断此时线程池正RUNNING,且可用工作线程为0,
else if (workerCountOf(recheck) == 0)
//添加新的非核心线程,并从workQueue中取出首个command运行
addWorker(null, false);
}
//队列可能已满从而失败的情况下,直接添加非核心工作线程,并将command作为task运行
else if (!addWorker(command, false))
//执行addWorker失败(线程池关闭或饱和)则执行拒绝策略
reject(command);
}
线程池中线程的5种状态:RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED.
ForkJoinPool
- 分解并汇总任务.
- 用很少线程执行很多任务(子任务),而ThreadPoolExecutor做不到子任务.
- CPU密集型线程池.
- 使用
Executors::newWorkStealingPool
可以便捷地创建FJP线程池. - jdk8提供的流式API中
Collection::parallelStream
(并行流)内部就是使用的FJP.
static void testFJP() throws Exception {
//实现一个RecursiveTask,如果实现RecursiveAction则是没有返回值的任务
class AddTask extends RecursiveTask<Long> {
public final int start,end;
public final int MAX_NUM = 1000;
public AddTask(int start, int end) {
this.start = start;this.end = end;
}
@Override
protected Long compute() {
var len = end-start;
if(len<=MAX_NUM){
var sum = 0L;
for(var i=start;i<end;++i) sum+=i;
return sum;
}else{
var middle = start+len/2;
var t1 = new AddTask(start,middle);
var t2 = new AddTask(middle,end);
t1.fork();t2.fork();//fork分出子任务
return t1.join()+t2.join();//将子任务join,当前线程等待子任务完成任务
}
}
}
//使用FJP执行计算密集任务,充分发挥CPU性能
System.out.println(new ForkJoinPool().submit(new AddTask(0, 9999)).get());
}
7.2. ThreadLocal
ThreadLocal
可以在当前线程存储一个变量,因此ThreadLocal具有线程隔离的效果.
其内部维护了一个ThreadLocalMap
,key为线程,value为set
设置的值.
如spring的声明式事务,就是将数据库连接存入ThreadLocal,以保证在同一个线程内的操作是属于同一个连接,从而实现事务.
var s = new ThreadLocal<String>();
s.set("asd");
System.out.println(s.get());
7.3. 强软弱虚引用
1. 强引用
普通的引用都是强引用.
如var o = new Object()
就创建了一个强引用.
强引用只有其不再被使用时,其内存才会被回收.
class MyObj {
@Override //被回收前执行的方法
protected void finalize() throws Throwable {System.out.println("我没了");}
}
var m = new MyObj();
System.gc();//第一次GC,m指向的对象不会被回收
Thread.sleep(1111);
System.out.println("睡了1111");
m=null;//使m指向的对象不再被使用
System.gc();//第二次GC,回收m指向的对象
System.exit(0);
/*控制台输出:
睡了1111
我没了
*/
2. 软引用
当内存不够时,即时该变量依然被引用还是会被回收.
创建软引用需要使用对象SoftReference
.
软引用可以用来做缓存.
//测试前需要将堆内存设置小一点,如 -Xms:20M -Xmx:20M
var s = new SoftReference<byte[]>(new byte[1024*1024*10]);
System.out.println(s.get());
System.gc();//不回收
System.out.println(s.get());
Thread.sleep(1000);
System.out.println("睡了1000");
System.gc();//不回收
var y = new byte[1024*1024*15];//填充内存,此时内存已不足以放下y,软引用将被回收以放下y
System.out.println(s.get());//输出null
System.exit(0);
3. 弱引用
只要发生gc,弱引用将被回收.
创建弱引用需要使用对象WeakReference
.
一般用在容器中,只要该对象的强引用不存在了,该对象就可以被回收了.
如ThreadLocal内部的Entry就是使用的WeakReference,即ThreadLocal持有某个变量时不影响其被回收,避免了内存泄漏.
var s = new WeakReference<byte[]>(new byte[1024]);
System.out.println(s.get());
System.gc();//只要发生gc,弱引用将被回收
System.out.println(s.get());//输出null
System.exit(0);
4. 虚引用
只要发生gc,虚引用指向的对象一定被回收,同时向queue中发送通知.
无法通过虚引用对象拿到其内的对象,它的唯一作用是被回收时发送一个通知.
创建虚引用需要使用对象PhantomReference
.
主要用在JVM中,和普通程序员无关.
如:当使用堆外内存时,使用该堆外内存的对象被回收时发送一个通知,通知某一个对象对该堆外对象进行处理.
class MyCls {
protected void finalize() throws Throwable{System.out.println("It is over.");}
}
var r = new ReferenceQueue<MyCls>();
var p = new PhantomReference<MyCls>(new MyCls(),r);
System.out.println(p.get());//get始终为null
var t = new Thread(()->{
for(;;){
try {Thread.sleep(100);} catch (InterruptedException e) {}
var s = r.poll();
if (s!=null) {
System.out.println("回收");
return;
}
System.gc();//gc将回收p内对象,并发送消息
}
});
t.start();
t.join();
/*控制台输出:
null
It is over.
回收
*/
7.4. VarHandle
jdk9提供的java.lang.invoke.VarHandle
,是一个指向变量的引用,可以对对象普通属性进行原子操作,比反射快(无需运行时检测,直接操作二进制码).
VarHandle
极大的提升了JUC下工具的效率,如使用VarHandle
优化了AbstractQueuedSynchronizer
更快的操作内部的链表.
public class XXX{
long x = 100L;
private static VarHandle varHandle;
static {
try {
varHandle = MethodHandles.lookup().findVarHandle(XXX.class,"x",long.class);
} catch (Exception e) { e.printStackTrace(); }
}
public static void main(String[] args) throws Exception {
var me = new XXX();
System.out.println("x="+varHandle.get(me));//100
varHandle.set(me,1000l);
System.out.println("x="+me.x);//1000
varHandle.compareAndSet(me,100l,10000l);//CAS
System.out.println("x="+me.x);//1000
}
}
7.5. ExecutorService和Callable及FutureTask
ThreadPoolExecutor
实现了ExecutorService
接口
Callable
是一个允许有返回值的任务
Callable
一般配合ExecutorService
使用,异步执行任务,在另一个线程同步等待结果.
常用在异步执行多个任务,在主线程阻塞等待所有异步任务返回结果.
Callable<String> callable = ()->"返回值";
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> submit = executorService.submit(callable);//异步执行任务
System.out.println(submit.get());//阻塞,直到获得返回值
FutureTask
是一个简化的版本,既是一个Task
,也是一个Future
.
var futureTask = new FutureTask<String>(()->"返回了一个值");
new Thread(futureTask).start();
System.out.println(futureTask.get());//阻塞
7.5. CompletableFuture
JDK8提供便捷的函数式(链式)异步工具类,内部使用了ForkJoinPool
.
期待JDK提供async
和await
关键字更方便的实现异步编程!
常用方法:
CompletableFuture::runAsync
:异步执行一个Runnable
.
CompletableFuture.runAsync(()-> System.out.print("异步任务."));
System.out.print("同步任务.");
//控制台输出: 同步任务.异步任务.
CompletableFuture::supplyAsync
:异步执行一个有返回值的任务
CompletableFuture::thenAcceptAsync
:接收上一个CompletableFuture
的返回值,并异步执行任务,thenAccept
是其同步版本.
CompletableFuture.supplyAsync(()->"异步任务返回").thenAcceptAsync(System.out::println);
CompletableFuture::anyOf
:任意一个任务完成执行接下来的任务.
CompletableFuture.anyOf(
CompletableFuture.runAsync(()-> {
try {Thread.sleep(100);} catch (InterruptedException e) {}
System.out.print("执行任务1.");
}),
CompletableFuture.runAsync(()-> System.out.print("执行任务2."))
).thenRun(()-> System.out.print("执行完成."));
//控制台输出: 执行任务2.执行完成.执行任务1.
CompletableFuture::allOf
:所有任务完成执行接下来的任务,用法同anyOf
.
CompletableFuture.allOf(
CompletableFuture.supplyAsync(()->"ret1").thenAccept(System.out::println),
CompletableFuture.runAsync(()-> System.out.println("run2"))
).join();//使当前线程阻塞,直到上面两个任务都完成
CompletableFuture::delayedExecutor
返回一个定时执行任务的Executor
.
CompletableFuture.delayedExecutor(1,TimeUnit.SECONDS).execute(()-> System.out.println("1秒后"));
8. 练习题
8.1. 线程交替运行
两个线程分别交替打印1-25和A-Z.
var lock = new Object();
var t1 = new Thread(()->{
for(var i='A';i<='Z';++i){
synchronized (lock){
lock.notify();
System.out.println("字母"+i);
try {
if(i!='Z')lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
var t2 = new Thread(()->{
for(var i=0;i<26;++i){
synchronized (lock) {
lock.notify();
System.out.println("数字"+i);
try {
if(i!=25)lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
8.2. 实现阻塞队列
简单实现阻塞队列,用以支撑生产者消费者模型
//阻塞队列
static class SyncQueue<T> {
private final List<T> list;
private final int cap;
private volatile int size = 0;
SyncQueue(int cap){
list = new LinkedList();
this.cap = cap;
}
public int getSize(){ return size; }
public synchronized void put(T val){
//使用while避免在被唤醒后即时已经到达cap也被放行
while (list.size() >= cap) doWait();
list.add(val);++size;
this.notifyAll();
}
public synchronized T get(){
while (list.size() <= 0) doWait();
T val = list.remove(0);--size;
this.notifyAll();
return val;
}
private void doWait(){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//生产者消费者模型
static void threadTest() throws Exception {
var q = new SyncQueue<Integer>(10);
var ts = new Thread[4];
for(var i=0;i<4;++i){
if(i%2!=0)
ts[i]=new Thread(()->{
for(var j=0;j<=20;++j){
var s = q.getSize();q.put(s);
System.out.println(Thread.currentThread().getName()+" put "+s);
}
});
else
ts[i]=new Thread(()->{
for(var j=0;j<=20;++j) System.out.println(Thread.currentThread().getName()+" get "+q.get());
});
}
for (Thread t : ts) t.start();
for (Thread t : ts) t.join();
}
分离阻塞及唤醒消费者生产者的阻塞队列
class SyncQueue<T> {
private final List<T> list;
private final int cap;
private volatile int size = 0;
private final ReentrantLock lock;
//使用Condition分离put和get
private final Condition conditionPut,conditionSet;
SyncQueue(int cap){
list = new LinkedList();
this.cap = cap;
lock = new ReentrantLock();
conditionPut = lock.newCondition();
conditionSet = lock.newCondition();
}
public int getSize(){ return size; }
public void put(T val){
try {
lock.lock();
while (list.size() >= cap) conditionPut.await();
list.add(val);++size;
conditionSet.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public T get(){
try {
lock.lock();
while (list.size() <= 0) conditionSet.await();
T val = list.remove(0);--size;
conditionPut.signalAll();
return val;
} catch (InterruptedException e) {
e.printStackTrace();
return null;
} finally {
lock.unlock();
}
}
}