JAVA多线程高并发基础知识点

JAVA多线程高并发基础知识点

本文中的代码java版本:jdk11.

1. 线程基础

1.1. 线程概念

线程是操作系统能够进行运算调度的最小单位(程序执行流的最小单元)。

它被包含在进程之中,是进程中的实际运作单位。

一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

1.2. 线程安全

  1. 原子性:线程共享内存同一时间只有一个线程可以操作(synchronized关键字,互斥锁)。
  2. 可见性:某个线程的操作可以立即被其他线程观察到(volatile关键字,强制刷新线程内该变量内存)。
  3. 有序性:线程内指令顺序执行(现代CPU指令重排,乱序执行);在java中指令重排不影响单线程内的最终结果,但不保证多线程。

1.3. 创建并启动线程的方法

  1. 实现 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();//阻止主线程在新线程之前结束
  1. 继承 Thread 类
class ByThread extends Thread{
    @Override
    public void run() {
        System.out.println("继承Thread并重写run方法");
    }
}
Thread thread = new ByThread();
thread.start();
thread.join();
  1. 守护线程
Thread thread = new Thread(()->System.out.println("lambda thread"));
//将线程设置为守护线程
//JVM会在所有非守护线程结束后自动退出,
//守护线程不会随着JVM退出而结束,
//因此守护线程如果持有需要关闭的资源,可能会因无法正常关闭资源(JVM退出)而造成严重后果,
//如打开文件可能会导致数据丢失.
thread.setDaemon(true);
thread.start();

1.4. 线程状态

使用Thread.currentThread().getState();可以获得当前线程状态,thread.getState();获得某一线程的状态。

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  3. 阻塞(BLOCKED):表示线程阻塞于锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。 java线程状态转换

1.5. 线程超时等待(TIMED_WAITING)相关方法

  1. 线程睡眠(sleep),当持有锁时不释放锁!且sleep的线程无法从外部唤醒,但可被中断!

    try {
        //睡眠1000毫秒,在此期间不参与CPU资源竞争(线程调度)
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        //可能在睡眠结束前线程就被中断了
        e.printStackTrace();
    }
    
  2. 线程让出(yield)

    //给CPU一个提示,让出本次线程调度,但参与下一次调度(进入线程等待队列),CPU可能会忽略本次让出
    Thread.yield();
    
  3. 线程加入(join)

    Thread t = new Thread(()->{
        System.out.println("new Thread.....");
    });
    t.start();
    //将线程t加入当前线程,即告诉当前线程等待线程t结束或在1000毫秒后再继续
    t.join(1000);
    
  4. 线程超时等待(wait),只有持有锁的对象才能调用这个方法且会释放锁!

    //使获得锁的obj对象所在线程进入等待,等待1000毫秒后自动被唤醒,或者被notify(notifyAll)唤醒
    obj.wait(1000);
    
  5. 线程超时禁用(park)

    //等待到当前时间的1000毫秒结束禁用
    LockSupport.parkUntil(System.currentTimeMillis()+1000);
    //禁用当前线程10000纳秒
    LockSupport.parkNanos(10000);
    

1.6. 线程等待(WAITING)相关方法

  1. 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();
    
  2. 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关键字

  1. synchronized是同步、互斥、独占、可重入的锁
  2. synchronized锁住的是对象,当没有传入对象时:
    1. 当对静态方法加锁时锁住的是当前类的class对象。
    2. 当对实例方法枷锁时锁住的时当前实例对象(this)。
    3. 在方法内使用synchronized时必须传入对象synchronized(obj){/*sync code*/}并锁住其代码块。
  3. synchronized锁住的对象才可以使用wait、notify、notifyAll。
  4. synchronized存在锁升级的概念
    1. 当始终同时只有同一个线程竞争时,锁时处于偏向锁状态(markword只记录线程标识而不尝试加锁且不主动释放记录的线程标识,此时默认不会有其他线程竞争锁)。
    2. 在获得锁的线程未结束时有线程来竞争锁,锁升级为轻量级锁(自旋锁,线程不调用系统函数进入CPU等待队列)。
    3. 当竞争变大(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 死锁

死锁的四个必要条件:

  1. 互斥:资源唯一,且线程互斥;
  2. 不可剥夺:已获得资源后不可剥夺资源,只能主动释放;
  3. 请求与保持:已获得资源的线程尝试获得其他唯一资源,而该资源已被其他线程占有;
  4. 循环等待:存在一种资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求.

避免死锁的核心思想: 打破死锁的四个必要条件

  1. 加锁顺序
  2. 加锁时限
  3. 死锁检测

避免死锁的常用方法:

  1. 有序资源分配法
  2. 银行家算法

解除死锁的常用方法:

  1. 设置线程还原点
  2. 撤销线程
  3. 剥夺资源

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.

StampedLockReadWriteLock的改进版,主要改进了读锁为乐观锁,提高了并发性能,但代价是可能读取到中间数据,需要业务自己判断取舍.

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关键字做同步,这个设计在高并发下性能低下,基本不再使用.

HashtableHashMap最大的区别是HashMap完全不加锁.

通过Collections.synchronizedMap(new HashMap<>())HashMap变为加锁的版本.

Vector和ArrayList

Vector是一个List容器,它使用synchronized关键字做同步,这个设计在高并发下性能低下,基本不再使用.

VectorArrayList最大的区别是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提供asyncawait关键字更方便的实现异步编程!

常用方法:

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();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章