并发编程笔记

源码
https://gitee.com/xiongyungang/studio

创建线程

Thread

重写run方法

        Thread thread = new Thread(){
            @Override
            public void run() {
                LOGGER.debug("run thread");
            }
        };
        thread.start();

Runnable

实现Runnable接口,作为参数传递到Thread类

//        Runnable runnable = new Runnable() {
//            public void run() {
//                LOGGER.debug("run thread");
//            }
//        };

        // lambda
        Runnable runnable = () -> LOGGER.debug("run thread");

        Thread thread = new Thread(runnable, "t1");
        thread.start();

Thread类中的Runnable

当成员target不为空时执行target的run方法

class Thread{
private Runnable target;

Thread(Runnable target){
   this.target = target;
}

@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

Callbale

Callable 区别与 Runnable : 有返回值且抛异常
image

        FutureTask futureTask = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                Thread.sleep(2000);
                LOGGER.debug("run thread");
                return 1;
            }
        });

        Thread t2 = new Thread(futureTask, "t2");
        t2.start();

        // 打印返回结果,堵塞2s
        Object o = futureTask.get();
        LOGGER.debug("{}", o);

Future接口

image

FutureTask实现了RunnableFuture,又实现Runnable、Future接口,了线程返回值等方法

public interface RunnableFuture<V> extends Runnable, Future<V> {

线程栈

  • 虚拟机栈:每个线程都会有一个独立的栈
  • 栈帧:线程中每个方法都是一个栈帧,栈帧中保存了该函数的返回地址和局部变量等信息
  • 活动栈帧:每个线程只有一个活动栈帧,就是正在执行的方法
    image

线程上下文切换

因为以下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码。
发生的原因,有如下几个:

  1. 线程的cpu时间片用完
  2. 垃圾回收
  3. 有更高优先级的线程需要运行
  4. 线程自己调用了sleep,yield,wait,join,park,sychronized,lock等方法

线程通讯

wait、notify

  • obj.wait(); 让object监视器的线程等待
  • obj.notify(); 让object上正在等待的线程中挑一个唤醒
  • obj.notifyAll(); 让object上正在等待的线程全部唤醒
new Thread(() -> {
            synchronized (lock) {
                try {
                    log.info("thread {} start..", Thread.currentThread().getName());
                    //lock.wait();
                    lock.wait(1000);
                    log.info("thread {} end..", Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();

//        Thread.sleep(2000);
//        synchronized (lock) {
//            lock.notify();
//        }
        log.info("main end..");

解决虚假唤醒


Park&Unpark

park方法的作用就是停下一个线程,unpark就是唤醒线程

Park&Unpark实现交替执行

/**
 * 输出:abcabcabcabcabc
 */
public class PackAndUnpack {
    static Thread t1;
    static Thread t2;
    static Thread t3;
    public static void main(String[] args) throws InterruptedException {
        t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                LockSupport.park();
                System.out.print("a");
                LockSupport.unpark(t2);
            }
        }, "t1");

        t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                LockSupport.park();
                System.out.print("b");
                LockSupport.unpark(t3);
            }
        }, "t2");

        t3 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                LockSupport.park();
                System.out.print("c");
                LockSupport.unpark(t1);
            }
        }, "t3");

        t1.start();
        t2.start();
        t3.start();

        LockSupport.unpark(t1);
    }
}

线程安全

临界区

一段代码块内如果存在对共享资源的多线程读写操作,这段代码块成为临界区

可见性

  • 可见性是指当一个线程修改了共享变量的值,其它线程能够适时得知这个修改
  • 共享变量:如果一个变量在多个线程的内存中都存在副本,那么这个变量就是这几个线程的共享变量

image

有序性

  • 有序性:编译器为了优化性能,有时候会改变程序中语句的先后顺序(指令重排),有序性指的是程序按照代码的先后顺序执行

synchronized

  • 修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得 当前对象的实例锁
  • 修饰静态方法:相当于给当前对象加锁,会作用域类的所有对象实例,进入同步代码前要获得当前class的锁。因为静态成员不属于任何一个类对象,(无论多少个对象都只有一份)。
  • 修饰代码块:指定加锁对象,对给定对象/类加锁,synchronized(this|object)表示进入同步代码块一定要获得该锁

1、synchronized关键字加到static静态方法和synchronized(class)代码块上实质都是给Class类上锁。
2、synchronized关键字加到实例方法上是给对象实例上锁
3、尽量不要使用synchronized(String a)因为jvm中,字符串常量池具有缓存功能

volatile

volatile关键字是用于保证有序性和可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值

  1. 保证可见性

volatile保证可见性底层原理是内存屏障
1.对volatile变量进行写指令时,加入写屏障
2.对volatile变量进行读指令时,加入读屏障
image

  1. 保证有序性

内存屏障同样保证了指令的有序性,禁用指令重排序
image

happens-before

happens-before是保证程序执行的原子性、可见性和有序性规则:
1、程序顺序原则。即在一个线程内必须保证语义的串行性(as-if-serial),也就是说按照代码顺序执行;
2、锁原则。解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,也就是说,在一个锁被解锁后,再加锁,那么加锁的动作必须在解锁动作之后(操作同一个锁);
3、volatile原则。volatile变量的写,发生与读之前,这样保证了volatile变量的可见性。简单理解就是volatile变量在每次被线程访问时,都强迫于从主内存中读取该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时候,不同的线程总能看到该变量的最新值;
4、线程启动原则。线程的start()方法先于它的每一个动作,即如果线程A在执行线程B的start方法之前修改了共享变量的值,那么当线程B执行start方法时,线程A对共享变量的修改对线程B可见;
5、传递性。A先于B,B先于C,那么A必然先于C;
6、线程终止原则。线程的所有操作先于线程的终结,Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B在终止之前,修改了共享变量,线程A从线程B的join方法成功返回后,线程B对共享变量的修改将对线程A可见;
7、线程中断原则。对线程interrupt()方法的调用先发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测线程是否中断;
8、对象的终结原则。对象的构造函数执行,结束先于finalize方法

ReentrantLock

特点:
1.可中断
2.可以设置超时时间
3.可以设置为公平锁
4.支持多个条件变量

基本语法

// 获取锁
reentrantLock.lock();
try {
    // 临界区
} finally {
    // 释放锁
    reentrantLock.unlock();
}

可重入

ReentrantLock 可重入锁:一个线程可以多次获取同一把锁·

static ReentrantLock lock = new ReentrantLock();

public static void main(String[] args) {
    method1();
}

public static void method1() {
    lock.lock();
    try {
        log.debug("execute method1");
        method2();
    } finally {
        lock.unlock();
    }
}

public static void method2() {
    lock.lock();
    try {
        log.debug("execute method2");
        method3();
    } finally {
        lock.unlock();
    }
}

public static void method3() {
    lock.lock();
    try {
        log.debug("execute method3");
    } finally {
        lock.unlock();
    }
}

可打断

  • lockInterruptibly()方法有竞争就进入阻塞队列等待,但可以被打断
ReentrantLock lock = new ReentrantLock();

Thread t1 = new Thread(() -> {
    log.debug("启动...");
    
    try {
        //没有竞争就会获取锁
        //有竞争就进入阻塞队列等待,但可以被打断
        lock.lockInterruptibly();
        //lock.lock(); //不可打断
    } catch (InterruptedException e) {
        e.printStackTrace();
        log.debug("等锁的过程中被打断");
        return;
    }
    
    try {
        log.debug("获得了锁");
    } finally {
        lock.unlock();
    }
}, "t1");

lock.lock();
log.debug("获得了锁");
t1.start();

try {
    sleep(1);
    log.debug("执行打断");
    t1.interrupt();
} finally {
    lock.unlock();
}

锁(可设置)超时

  • tryLock() 方法可指定超时时间,超时后释放锁
ReentrantLock lock = new ReentrantLock();

Thread t1 = new Thread(() -> {
    log.debug("启动...");
    
    try {
        if (!lock.tryLock(1, TimeUnit.SECONDS)) {
            log.debug("获取等待 1s 后失败,返回");
            return;
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    try {
        log.debug("获得了锁");
    } finally {
        lock.unlock();
    }
}, "t1");

lock.lock();
log.debug("获得了锁");
t1.start();

try {
    sleep(2);
} finally {
    lock.unlock();
}

(可设置是否为)公平锁

公平: 先来就能先执行
不公平: 不保证先来就先执行

  • ReentrantLock 默认非公平锁,可在构造方法中指定为公平锁
ReentrantLock lock = new ReentrantLock(false);

(多个)条件变量

条件变量最主要的作用是用来管理线程执行对某些状态的依赖性

ReentrantLock可针对多个Condition,每个Condition处理不同逻辑

static ReentrantLock lock = new ReentrantLock();

static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();

static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;

public static void main(String[] args) {
    
    new Thread(() -> {
        try {
            lock.lock();
            while (!hasCigrette) {
                try {
                    waitCigaretteQueue.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("等到了它的烟");
        } finally {
            lock.unlock();
        }
    }).start();
    
    new Thread(() -> {
        try {
            lock.lock();
            while (!hasBreakfast) {
                try {
                    waitbreakfastQueue.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("等到了它的早餐");
        } finally {
            lock.unlock();
        }
    }).start();
    
    sleep(1);
    sendBreakfast();
    sleep(1);
    sendCigarette();
}

private static void sendCigarette() {
    lock.lock();
    try {
        log.debug("送烟来了");
        hasCigrette = true;
        waitCigaretteQueue.signal();
    } finally {
        lock.unlock();
    }
}

private static void sendBreakfast() {
    lock.lock();
    try {
        log.debug("送早餐来了");
        hasBreakfast = true;
        waitbreakfastQueue.signal();
    } finally {
        lock.unlock();
    }
}

CAS

原子类的compareAndSet方法,比较要修改的值和最新的值是一致,一致则更新

  • CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果
/**
 * 无锁实现共享资源的安全性
 */
public class CasDemo {
    public static void main(String[] args) {
        Account account = new AccountSync(10000);
        Account.demo(account);

        Account account2 = new AccountSafe(10000);
        Account.demo(account2);

    }
}

/**
 * 无锁方式,原子类(重试)
 */
class AccountSafe implements Account {

    private AtomicInteger balance; //原子整数

    public AccountSafe(Integer balance) {
        this.balance = new AtomicInteger(balance);
    }

    @Override
    public Integer getBalance() {
        return balance.get();
    }

    @Override
    public void withdraw(Integer amount) {
        while (true) {
            int prev = balance.get();
            int next = prev - amount;
            // 判断balance和prev是否相等
            if (balance.compareAndSet(prev, next)) {
                break;
            }
        }
        // 可以简化为下面的方法
        // balance.addAndGet(-1 * amount);
    }
}

/**
 * 同步锁方式(堵塞)
 */
class AccountSync implements Account{
    private Integer value;

    public AccountSync(Integer value) {
        this.value = value;
    }

    @Override
    public Integer getBalance() {
        return value;
    }

    @Override
    public synchronized void withdraw(Integer amount) {
        value = value - amount;
    }
}

interface Account {

    // 获取余额
    Integer getBalance();

    // 取款
    void withdraw(Integer amount);

    /**
     * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
     * 如果初始余额为 10000 那么正确的结果应当是 0
     */
    static void demo(Account account) {
        List<Thread> ts = new ArrayList<>();

        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(10);
            }));
        }
        ts.forEach(Thread::start);

        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();

        System.out.println(account.getBalance()
                + " cost: " + (end-start)/1000_000 + " ms");
    }
}

特点:

结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。

● CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
● synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
● CAS 体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思
○ 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
○ 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响

原子类

原子整数

● AtomicBoolean
● AtomicInteger
● AtomicLong

AtomicInteger i = new AtomicInteger(0);

// 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++
System.out.println(i.getAndIncrement());

// 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++i
System.out.println(i.incrementAndGet());

// 自减并获取(i = 2, 结果 i = 1, 返回 1),类似于 --i
System.out.println(i.decrementAndGet());

// 获取并自减(i = 1, 结果 i = 0, 返回 1),类似于 i--
System.out.println(i.getAndDecrement());

// 获取并加值(i = 0, 结果 i = 5, 返回 0)
System.out.println(i.getAndAdd(5));

// 加值并获取(i = 5, 结果 i = 0, 返回 0)
System.out.println(i.addAndGet(-5));

// 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.getAndUpdate(p -> p - 2));

// 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.updateAndGet(p -> p + 2));

// 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
// getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
// getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
System.out.println(i.getAndAccumulate(10, (p, x) -> p + x));

// 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));

原子引用

● AtomicReference
● AtomicMarkableReference
● AtomicStampedReference

class DecimalAccountSafeCas implements DecimalAccount {
    AtomicReference<BigDecimal> ref;
    
    public DecimalAccountSafeCas(BigDecimal balance) {
        ref = new AtomicReference<>(balance);
    }
    
    @Override
    public BigDecimal getBalance() {
        return ref.get();
    }
    
    @Override
    public void withdraw(BigDecimal amount) {
        while (true) {
            BigDecimal prev = ref.get();
            BigDecimal next = prev.subtract(amount);
            if (ref.compareAndSet(prev, next)) {
                break;
            }
        }
    }
    
}

public interface DecimalAccount {
    // 获取余额
    BigDecimal getBalance();
    
    // 取款
    void withdraw(BigDecimal amount);
    
    /**
    * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
    * 如果初始余额为 10000 那么正确的结果应当是 0
    */
    static void demo(DecimalAccount account) {
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(BigDecimal.TEN);
            }));
        }
        ts.forEach(Thread::start);
        
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(account.getBalance());
    }
    
}

ABA 问题

主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,如果主线程希望:
只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号

static AtomicReference<String> ref = new AtomicReference<>("A");

public static void main(String[] args) throws InterruptedException {
    log.debug("main start...");
    // 获取值 A
    // 这个共享变量被它线程修改过?
    String prev = ref.get();
    
    other();
    
    sleep(1);
    // 尝试改为 C
    log.debug("change A->C {}", ref.compareAndSet(prev, "C"));
}

private static void other() {
    
    new Thread(() -> {
        log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
    }, "t1").start();
    
    sleep(0.5);
    
    new Thread(() -> {
        log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
    }, "t2").start();
    
}

ABA问题的解决

AtomicStampedReference(维护版本号)、AtomicMarkableReference(仅维护是否修改过)

  1. AtomicStampedReference(维护版本号)

维护一个版本号,每次修改原子类时,版本号递增

static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);

public static void main(String[] args) throws InterruptedException {
    log.debug("main start...");
    // 获取值 A
    String prev = ref.getReference();
    // 获取版本号
    int stamp = ref.getStamp();
    log.debug("版本 {}", stamp);
    // 如果中间有其它线程干扰,发生了 ABA 现象
    other();
    sleep(1);
    // 尝试改为 C
    log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
}

private static void other() {
    new Thread(() -> {
        log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B", 
                                                      ref.getStamp(), ref.getStamp() + 1));
        log.debug("更新版本为 {}", ref.getStamp());
    }, "t1").start();
    
    sleep(0.5);
    
    new Thread(() -> {
        log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A", 
                                                      ref.getStamp(), ref.getStamp() + 1));
        log.debug("更新版本为 {}", ref.getStamp());
    }, "t2").start();
}
  1. AtomicMarkableReference(仅维护是否修改过)

只需要判断原子类是否被修改过


/**
 * CAS解决ABA问题:AtomicMarkableReference(仅维护是否修改过)
 */
import java.util.concurrent.atomic.AtomicMarkableReference;

    public class AtomicMarkableReferenceDemo {
    private static final Logger log= LoggerFactory.getLogger(AtomicMarkableReferenceDemo.class);

    static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A", true);

    public static void main(String[] args) throws InterruptedException {
        log.debug("main start...");
        // 获取值 A
        String prev = ref.getReference();
        log.debug("是否修改过 {}", !ref.isMarked());
        // 如果中间有其它线程干扰,发生了 ABA 现象
        other();
        Thread.sleep(1000);
        // 尝试改为 C
        log.debug("change A->C {}", ref.compareAndSet(prev, "C", true, false));
    }

    private static void other() throws InterruptedException {
        new Thread(() -> {
            log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
                    true, false));
            log.debug("是否修改过 {}", !ref.isMarked());
        }, "t1").start();

        Thread.sleep(500);

        new Thread(() -> {
            log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
                    true, false));
            log.debug("是否修改过 {}", !ref.isMarked());
        }, "t2").start();
    }
}

线程池

ThreadPoolExecutor

  • 线程池状态

hreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
image
这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作进行赋值 --

// c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));

// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }

ThreadPoolExecutor

● corePoolSize 核心线程数目 (最多保留的线程数)
● maximumPoolSize 最大线程数目
● keepAliveTime 生存时间 - 针对救急线程
● unit 时间单位 - 针对救急线程
● workQueue 阻塞队列
● threadFactory 线程工厂 - 可以为线程创建时起个好名字
● handler 拒绝策略

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                         RejectedExecutionHandler handler)

工厂方法创建线程池

newFixedThreadPool

适用于任务量已知,相对耗时的任务

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

newCachedThreadPool

整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。
适合任务数比较密集,但每个任务执行时间较短的情况

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

newSingleThreadExecutor

希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。
任务执行完毕,这唯一的线程也不会被释放。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

和自己创建一个线程来工作的区别:
● 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作

和Executors.newFixedThreadPool(1)的区别
● Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
○ FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
● Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
○ 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改

提交线程池的方法

// 执行任务
void execute(Runnable command);

// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);

// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
    throws InterruptedException;

// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                              long timeout, TimeUnit unit)
    throws InterruptedException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
    throws InterruptedException, ExecutionException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
                long timeout, TimeUnit unit)
 throws InterruptedException, ExecutionException, TimeoutException;

关闭线程池

/*
线程池状态变为 SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完
- 此方法不会阻塞调用线程的执行
*/
void shutdown();

/*
线程池状态变为 STOP
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> shutdownNow();

// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();

// 线程池状态是否是 TERMINATED
boolean isTerminated();

// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

应用

public class TestStarvation {
    
    static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");
    static Random RANDOM = new Random();
    
    static String cooking() {
        return MENU.get(RANDOM.nextInt(MENU.size()));
    }
    
    public static void main(String[] args) {
        ExecutorService waiterPool = Executors.newFixedThreadPool(1);
        ExecutorService cookPool = Executors.newFixedThreadPool(1);
        
        waiterPool.execute(() -> {
            log.debug("处理点餐...");
            Future<String> f = cookPool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        
        waiterPool.execute(() -> {
            log.debug("处理点餐...");
            Future<String> f = cookPool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        
    }
}
posted @ 2022-08-04 13:35  熊云港  阅读(29)  评论(0编辑  收藏  举报