Java编程思想-并发

21 并发

21.0 基本概念

公平锁: 每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁。例如收费站 张三、李四、王五按照顺序通过收费站。

非公平锁: 每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,所有线程会竞争获取锁。例如收费站 张三、李四正在排队,但是王五直接插队到张三后面,那么通过收费站顺序为 张三、王五、李四。其中锁 synchronized 和 ReentrantLock 默认都是非公平锁,虽然ReentrantLock可由参数设置为公平锁。非公平锁一般情况下系统的吞吐量更高。

可重入锁: 一个线程已经获取锁A时,在执行过程中还可以再获取锁A,此为可重入锁。例如读写锁ReentrantLock就属于可重入锁:先获取写锁后,执行过程中还可以在获取读锁。

锁升级: 一个锁分为写锁和读锁,一个线程已获取读锁后,执行过程中再获取写锁,由读锁升级至优先级更高的写锁称之为锁升级,一般情况下不支持。

锁降级: 一个锁分为写锁和读锁,一个线程已获取写锁后,执行过程中再获取读锁,由写锁降级至优先级更低的读锁称之为锁降级,Java默认读写锁ReentrantLock即支持此种情况。

21.1 并发的多面性

  • 操作系统通常会将进程互相隔离开,使得进程之前相互不干涉。但是操作系统对于进程通常会有数量和开销的限制,导致进程不能无限创建。
  • Java中线程调度采用抢占式,表示调度机制会周期性的中断线程,切换上下文至另一线程,使得每个线程均有机会分到合理的时间片用以执行。

21.2 基本的线程机制

  • 任务由线程驱动,Java中定义任务均需implements Runnable接口,实现run()方法;而要运行此任务,则必须将已定义的任务附着到线程上,即:
        new Thread(new Runnable() {
            @Override
            public void run() {
                //run something
            }
        }).start();
  • main中创建线程时,与创建其他对象有一定区别:没有捕获任何对线程对象的引用。 但实际上每个Thread对象都“注册”了他自己,因此确实有一个对此Thread对象的引用,并且在Thread对象包含的任务在run()退出之前,垃圾回收器无法清除此引用。
  • Java线程执行器: Executor 允许管理异步任务的执行,而无须显示的管理线程的生命周期,主要分类为以下几个:
        // 一次性预先执行代价高昂的线程分配,后续不再分配已不再回收,可限定线程的数量
        ExecutorService service1 = Executors.newFixedThreadPool(5);
        // 程序执行过程中创建和所需数量相同的线程,在回收旧线程时[默认60s无任务运行时中断且回收旧线程]停止创建新线程,线程可动态调整。
        ExecutorService service2 = Executors.newCachedThreadPool();
        // 仅创建一个线程,当提交多个任务时,则任务采用排队方法
        ExecutorService service3 = Executors.newSingleThreadExecutor();
        // 开启定时任务,周期性执行确定任务
        ExecutorService service4 = Executors.newScheduledThreadPool(4);
		// Timer类的定时任务,相隔1000ms输出一次Hello Word
		new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("Hello World...");
            }
        }, 0, 1000);

        // 执行任务,无对应返回值
        service1.execute(new Runnable() {
            @Override
            public void run() {
                // do someThing
            }
        });
        // 执行任务,有对应返回值,其中Future可认为是对应线程句柄,强制中断某个线程可使用ret.setCancel(true)。
        Future<String> ret = service1.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                // do someThing
                return "";
            }
        });
        // 获得返回值,get方法将阻塞主线程直至获取最终结果
        ret.get();
        // 强制中断此线程
        ret.setCancel(true);
        
        // 拒绝新任务再次提交,但是会继续运行之前提交的任务直至完成,在继续运行之前提交的任务时主线程继续运行
        service1.shutdown();
        // 拒绝新任务再次提交,且中断停止所有正在执行的任务,在中断之前提交的线程任务时主线程继续运行
        service1.shutdownNow();
        // 主线程阻塞100s或者之前提交的任务运行完毕后,主线程继续往下运行
        service1.awaitTermination(100, TimeUnit.SECONDS);
  • 线程休眠:可以调用Thread.sleep(100)或者TimeUnit.MICROSECONDS.sleep(100),以上调用均会抛出异常。但是任务中抛出的异常仅能在本任务中处理,而不能扩线程传播至主线程中
  • 线程异常处理:线程中如若出现异常,此异常一旦逃出线程的run()后,则不能在主线程中捕获,而只能向外传播至控制台,除非采用特殊步骤捕获此种异常(“Thread.UncaughtExceptionHandler类”)。如下所示:
public class TestUncaughtExceptionHandler {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool(new MyThreadFactory());
        executorService.execute(new MyRunable());
        // 线程关闭,不再接受新任务
        executorService.shutdown();
        // 等待执行器executorService线程池中的线程执行完毕后,再执行main线程,设置1000s超时时间
        executorService.awaitTermination(1000, TimeUnit.SECONDS);
        System.out.println("Task has completed.");
    }
}

class MyRunable implements Runnable {
    @Override
    public void run() {
        Thread t = Thread.currentThread();
        System.out.println("Thread name: " + t.getName() + " has begin.");
        System.out.println("unCaughtExceptionHandler is: " + t.getUncaughtExceptionHandler());
        throw new RuntimeException();
    }
}

class MyThreadFactory implements ThreadFactory {
    private int index = 0;
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setName("asr-thread-" + index);
        t.setPriority(Thread.NORM_PRIORITY);
        // 设置线程的UncaughtExceptionHandler,用以处理线程run方法抛出异常,若不处理,则异常直接向外传播至控制台
        t.setUncaughtExceptionHandler(
            (t1, e) -> System.out.println("Thread name: " + t1.getName() + " has exception: " + e.getMessage()));
        ++index;
        return t;
    }
}
  • 线程优先级:在Java中,所有线程如若不指定,均采用默认优先级执行,而试图操作线程的优先级往往不科学,编码中尽量不使用setPriority()方法操作某个线程的优先级。
  • 线程让步:Java支持线程让步操作(亦即让出CPU供其他线程使用),而采用让步操作(Thread.yield())时,仅是建议CPU执行其他相同优先级的线程,注意仅是建议,而具体是否执行由CPU调用决定
  • 后台线程:指提供给后台的一种通用服务的线程。当所有的非后台线程执行完毕时,程序即终止,同时也会立即杀死所有的后台线程(亦即调用了interrupted()方法,即使后台线程中将要执行finally子句时也会立即杀死,不再执行finally子句);并且若一个线程为后台线程,那么此线程创建的任何线程均自动设置为后台线程。设置后台线程方法如下:
public class TestDeamonThread {
    public static void main(String[] args) throws InterruptedException {
        // ExecutorService采用DaemonThreadFactory,若不设置,默认采用ThreadFactory
        ExecutorService executorService = Executors.newCachedThreadPool(new DaemonThreadFactory());
        System.out.println("All daaemon thread started...");
        for (int i = 0; i < 10; i++) {
            executorService.execute(new DaemonFromFactory());
        }
        TimeUnit.SECONDS.sleep(10);
    }
}

class DaemonFromFactory implements Runnable {
    @Override
    public void run() {
        try {
            while (true) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("Thread name: " + Thread.currentThread().getName() + " has executed.");
            }
        } catch (InterruptedException e) {
            System.out.print("Thread name: " + Thread.currentThread().getName() + " is interrupted.");
        }
    }
}

// 工厂方法,implements ThreadFactory
class DaemonThreadFactory implements ThreadFactory {
    private int index = 0;
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        // 设置线程为后台线程,默认为false
        t.setDaemon(true);
        t.setName("asr-thread-" + index);
        ++index;
        return t;
    }
}

21.3 线程的同步

线程同步指多线程之间因共享资源或者依赖关系等,存在着其中一个线程等待另一个线程执行完毕后,再继续执行。

21.3.1 Join操作

Join操作目的在于线程A等待另一线程B执行完毕后,再执行线程B对应的方法。例如:Joiner线程中调用Sleeper.join()方法,那么则待 Sleeper线程执行完毕后,Joiner线程再执行。

Join方法执行过程中,可通过Sleeper.interrupt()方法打断(Sleeper线程中需处理异常),直接执行Joiner线程。

public class TestJoinOps {
    public static void main(String[] args) {
        Sleeper sleeper1 = new Sleeper("sleep1", 2);
        Sleeper sleeper2 = new Sleeper("sleep2", 2);

        Joiner joiner1 = new Joiner("joiner1", sleeper1);
        Joiner joiner2 = new Joiner("joiner2", sleeper2);
        // sleeper2调用interrupt()方法,打断sleeper2.join操作,catch sleeper2异常,授限制性joiner2操作
        sleeper2.interrupt();
    }
}

class Sleeper extends Thread {
    private int duration;
    private String name;
    public Sleeper(String name, int duration) {
        super(name);
        this.duration = duration;
        start();
    }
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(duration);
        } catch (InterruptedException e) {
            System.out.println("Thread name: " + getName() + " is interrupted.");
            return;
        }
        System.out.println("Thread name: " + getName() + " is waked.");
    }
}

class Joiner extends Thread {
    private Sleeper sleeper;
    private String name;
    public Joiner(String name, Sleeper sleeper) {
        super(name);
        this.sleeper = sleeper;
        start();
    }
    @Override
    public void run() {
        try {
            // Joiner线程中调用sleeper.join操作,故而sleeper线程优先执行
            sleeper.join();
        } catch (InterruptedException e) {
            System.out.println("Thread name: " + getName() + " is interrupted.");
        }
        System.out.println("Thread name: " + getName() + " join completed.");
    }
}

21.3.2 锁操作

synchronized关键字:

  1. Java中每个对象都自动含有单一的锁。当任务调用synchronized标识的方法时,那么此任务将自动获得本对象的锁(对某个对象来说,所有synchronized关键字标识的方法将共享同一个锁),在调用上一个synchronized方法完成后(任务自动释放对象锁),其他任务或者下一个synchronized方法才能被调用。
  2. 一个任务可以多次获得某个对象的锁(如任务A调用对象B的synchronized方法B1,而方法B1又调用了对象B的synchronized方法B2,那么任务A就获得对象B的锁2次),JVM会跟踪对象被加锁的次数,随着完成一个synchronized方法,计数减1,直至0时,对象锁被完全释放。
  3. 同样的针对于每一个类,也有一个类锁(作为类Class对象的一部分),采用synchronized static关键字可以在类的范围内防止对static数据的并发访问。

LOCK关键字:

  1. Lock对象必须被显示创建、锁定和释放
  2. 相对于synchronized关键字,此Lock对象更加灵活(若synchronized关键字标识的方法中某些事务中途失败了,但是可能导致对象的状态已改变。而这种情况,Lock对象可以在finally子句中将对象复原)。
  3. ReentrantLock锁允许获取锁,若如获取失败不用等待可以继续做其他事情
public class TestLockOps {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(10, new ThreadFactory() {
            private int index = 0;
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("asr-thread-" + index);
                ++index;
                return thread;
            }
        });
        System.out.println("Threads begain...");
        IntGenerator intGenerator = new IntGenerator();
        for (int j = 0; j < 10; j++) {
            service.execute(new IntConsumer(intGenerator));
        }
        service.shutdown();
        service.awaitTermination(1000, TimeUnit.SECONDS);
    }
}

class IntConsumer implements Runnable {
    private IntGenerator generator;
    public IntConsumer(IntGenerator generator) {
        this.generator = generator;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            int index = generator.next();
            System.out.println("Thread name: " + Thread.currentThread().getName() + ", and index is: " + index);
        }
    }
}

class IntGenerator {
    private final Lock lock = new ReentrantLock();
    private int index;
    public int next() {
        // 此lock.lock代码段用以锁住此对象(此对象是共享变量)
        lock.lock();
        try {
            // 睡眠1s,防止线程无需调用导致index无序输出
            TimeUnit.SECONDS.sleep(1);
            ++index;
            return index;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 必须放在finally子句中,且return必须放在try中,确保return之前unlock不会过早发生
            // 若不释放,则只会有一个线程获得锁,只有一个线程正常往下走
            lock.unlock();
        }
        return 0;
    }
}

信号量机制:

  • 与通常的Lock或者synchronized关键字不同(保证任一时刻只允许一个线程/任务访问一项资源);信号量机制(Semaphore)则允许n个任务可同时访问这个资源。
public class TestSemaphore {
    public static void main(String[] args) throws InterruptedException {
        int size = 10;
        // Pool中定义了信号量及计数值,保证同一时刻只有计数值个线程/任务访问
        Pool<Fat> pool = new Pool<Fat>(Fat.class, size);
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < size; i++) {
            service.execute(new SemaphoreTask<Fat>(pool));
        }
        TimeUnit.SECONDS.sleep(1);
        List<Fat> list = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            Fat fat = pool.checkOut();
            System.out.println("Main thread checkout task:" + fat);
        }
    }
}

/**
 * 信号量任务
 * 
 * @param <T>
 */
class SemaphoreTask<T> implements Runnable {
    private final Pool<T> pool;
    public SemaphoreTask(Pool<T> pool) {
        this.pool = pool;
    }
    @Override
    public void run() {
        try {
            // 获得一个元素
            T item = pool.checkOut();
            System.out.println("SemaphoreTask checkout task: " + item);
            TimeUnit.SECONDS.sleep(1);
            // 释放此元素
            pool.checkIn(item);
            System.out.println("SemaphoreTask checkin task: " + item);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Pool<T> {
    private int size;
    private Semaphore semaphore;
    private List<T> items;
    private boolean[] usable;
    public Pool(Class<T> tClass, int size) {
        this.size = size;
        // 初始化信号量,采用先进先出公平分配方式
        this.semaphore = new Semaphore(size, true);
        this.items = new ArrayList<>(size);
        this.usable = new boolean[size];
        try {
            for (int i = 0; i < size; i++) {
                items.add(tClass.newInstance());
            }
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    // 依据信号量获得元素
    public T checkOut() {
        // 获得信号量
        try {
            semaphore.acquire();
            return getItem();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
    // 回收元素至items列表
    public void checkIn(T t) {
        // 释放此信号量
        semaphore.release();
        releaseItem(t);
    }
    // 获得元素
    public synchronized T getItem() {
        for (int i = 0; i < this.size; i++) {
            if (!usable[i]) {
                return items.get(i);
            }
        }
        return null;
    }
    // 释放元素
    public synchronized boolean releaseItem(T t) {
        int index = items.indexOf(t);
        if (index == -1) {
            System.out.println("item t not existed in items...");
            return false;
        }
        usable[index] = true;
        return true;
    }
}

class Fat {
    private volatile double b;
    private static int count = 0;
    private final int id = count++;
    public Fat() {
        for (int i = 0; i < 1000; i++) {
            b += (Math.PI + Math.E) / (double) i;
        }
    }
    @Override
    public String toString() {
        return "Fat{" + "b=" + b + ", id=" + id + "}";
    }
}

21.3.3 原子操作

原子操作:原子操作指不可再分的操作,要么成功要么失败,中间不存在其他状态。在Java中:基本数据类型变量(除long和double,double和long类型长度均为64bit,JVM可将此种类型的读取和写入当做两个分离的32bit,但由于目前各种平台下的商用虚拟机几乎都选择把64位数据的读写操作作为原子操作来对待,因此在编写代码时一般也不需要将用到的long和double变量专门声明为volatile)、引用类型变量声明为volatile的任何类型变量的访问均是具备原子性。

volatile关键字:

  1. volatile关键字定义的域,更新时会立即刷入主存,同样此域也必须从主存中读取(而非volatile关键字定义的域更新无需立即刷入主存,可在寄存器/高速缓存中刷新使用,最后再刷入主存)。
  2. 同步代码块或者方法防护的域: a, 在对一个变量执行lock操作,将会清空寄存器/高速缓存中此变量的值,在执行引擎使用这个变量前需要重新执行load(从主存)或assign操作初始化变量的值。b,对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store和write操作)。c, Java内存规范要求lock和unlock必须成对出现(详见wiki JAVA内存模型)。
  3. volatile关键字和同步代码块都要求读取从主存读取,volatile定义的域更新后的值必须立即写入主存;而同步代码块定义的域保证最多只有一个线程访问,在unlock操作前,必须将更新后的值写入主存
  4. 但当一个域的值依赖于此域上一个状态的值时,volatile关键字无法工作(因为像 i++/i-- 操作非原子操作,存在getfield操作和putfield操作等做个操作,导致多个线程访问时存在状态错乱)。如下述所示:
public class TestAtomicOps {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        NumberGenerate generate = new NumberGenerate();
        for (int i = 0; i < 10; i++) {
            service.execute(new AtomicTask(generate));
        }
        service.shutdown();
        service.awaitTermination(10000, TimeUnit.SECONDS);
    }
}

class AtomicTask implements Runnable {
    private NumberGenerate generate;
    public AtomicTask(NumberGenerate generate) {
        this.generate = generate;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100000; i++) {
            System.out.println(
                "Thread name: " + Thread.currentThread().getName() + ", and next number is: " + generate.nextNumber());
        }
    }
}

class NumberGenerate {
    // 当volatile修饰的值状态更新时不依赖于上个状态(是原子操作时),多个线程无需显示指定同步关键字
    private volatile int number;
    // synchronized关键字修饰方法,若不存在导致多线程更新number值时错乱;若存在此关键字,即使无volatile修饰,也不会乱
    public synchronized int nextNumber() {
        return ++number;
    }
}

原子类:Java中也提供了AtomicInteger, AutomicLong等方法,用于保证对于int类型和long类型的所有操作均保证原子化,包含自增和自减操作。

21.3.4 线程协作

线程协作通常是指多个线程为完成某个具体任务而合作起来。
wait()和notify()/notifyAll():

  1. 在一个已获取锁的方法中,调用sleep()方法和yield()方法后,并不释放锁。
  2. 在一个已获取锁的方法中,调用wait()方法可以将锁释放,执行线程将被挂起(也即意味着其他获取锁的方法可以不受影响的执行)。
  3. 调用wait()方法时,可设置等待一段时间后继续运行,亦可等待notify/notifyAll方法唤醒后运行。
  4. wait()方法、notify()/notifyAll()方法中会自动将锁释放,故而调用以上方法时必须在同步方法(synchronized标识方法)或者同步块(synchronized块和lock块)中执行
  5. notify()方法和notifyAll()方法区别,notify方法只会在众多等待同一个锁的任务中唤醒其中一个任务,而notifyAll方法则会唤醒所有等待同一个锁的任务。
public class TestWaitNotifyOps {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        // Car为共享资源
        Car car = new Car();
        service.execute(new Buffed(car));
        service.execute(new WaxOn(car));
        TimeUnit.MINUTES.sleep(1);
        service.shutdownNow();
    }
}

class Car {
    private boolean waxOn = false;
    private int waitForWaxNum;
    private int waitForBuffNum;
    // 涂蜡操作
    public synchronized void waxed() {
        waxOn = true;
        notify();
    }
    // 抛光操作
    public synchronized void buffed() {
        waxOn = false;
        notifyAll();
    }
    // 等待涂蜡操作
    public synchronized void waitForWax() throws InterruptedException {
        // 必须为while循环操作
        while (!waxOn) {
            ++waitForWaxNum;
            // System.out.println("waitForWaxNum:" + waitForWaxNum);
            wait();
        }
    }
    // 等待抛光操作
    public synchronized void waitForBuff() throws InterruptedException {
        while (waxOn) {
            ++waitForBuffNum;
            System.out.println("waitForBuffNum:" + waitForBuffNum);
            wait();
        }
    }
}

class WaxOn implements Runnable {
    private final Car car;
    public WaxOn(Car car) {
        this.car = car;
    }
    @Override
    public void run() {
        try {
            // while循环,一直运行,当线程中断时退出
            while (!Thread.interrupted()) {
                System.out.println("waxOn! ");
                car.waxed();
                TimeUnit.SECONDS.sleep(2);
                car.waitForBuff();
            }
        } catch (InterruptedException e) {
            System.out.println("WaxOn Exception: " + e.getMessage());
        }
    }
}

class Buffed implements Runnable {
    private final Car car;
    public Buffed(Car car) {
        this.car = car;
    }
    @Override
    public void run() {
        try {
            // while循环,一直运行,当线程中断时退出
            while (!Thread.interrupted()) {
                System.out.println("Buffed! ");
                car.buffed();
                TimeUnit.SECONDS.sleep(1);
                car.waitForWax();
            }
        } catch (InterruptedException e) {
            System.out.println("Buffed Exception: " + e.getMessage());
        }
    }
}

同步队列:
同步队列在任一时刻都只允许一个任务插入或移除元素,生产者 -> 同步队列 -> 消费者;若队列为空时,消费者线程阻塞,队列为满时,生产者线程阻塞。

public class TestBlockQueue {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Toast> toastQueue = new LinkedBlockingQueue<>();
        BlockingQueue<Toast> buffQueue = new LinkedBlockingQueue<>();
        BlockingQueue<Toast> finishQueue = new LinkedBlockingQueue<>();

        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(new Toaster(toastQueue));
        service.execute(new Butter(toastQueue, buffQueue));
        service.execute(new Jammer(buffQueue, finishQueue));
        service.execute(new Consumer(finishQueue));
        TimeUnit.MINUTES.sleep(2);
        service.shutdownNow();
    }
}

class Toast {
    public enum Status {
        DRY,
        BUTTERED,
        JAMMED;
    }
    public Status status = Status.DRY;
    public int id;
    public Toast(int id) {
        this.id = id;
    }
    public void butter() {
        this.status = Status.BUTTERED;
    }
    public void jam() {
        this.status = Status.JAMMED;
    }
    public Status getStatus() {
        return status;
    }
    public int getId() {
        return id;
    }
    @Override
    public String toString() {
        return "Toast{" + "status=" + status + ", id=" + id + '}';
    }
}

/**
 * 制作吐司任务
 */
class Toaster implements Runnable {
    // 制作完成的吐司加入BolockQueue
    private final BlockingQueue<Toast> toasterQueue;
    private int toastId;
    public Toaster(BlockingQueue<Toast> blockingQueue) {
        this.toasterQueue = blockingQueue;
    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                TimeUnit.SECONDS.sleep(2);
                Toast toast = new Toast(++toastId);
                System.out.println(
                    "Toaster : create toast, id is " + toast.getId() + ", and status is: " + toast.getStatus());
                toasterQueue.add(toast);
            }
        } catch (InterruptedException e) {
            System.out.println("Toaster : " + "InterruptedException");
        }
    }
}

/**
 * 涂黄油任务
 */
class Butter implements Runnable {
    // 制作好的吐司
    private final BlockingQueue<Toast> toasterQueue;
    // 涂黄油完成的任务
    private final BlockingQueue<Toast> butterQueue;
    public Butter(BlockingQueue<Toast> toasterQueue, BlockingQueue<Toast> butterQueue) {
        this.toasterQueue = toasterQueue;
        this.butterQueue = butterQueue;
    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                TimeUnit.SECONDS.sleep(1);
                // 从制作好的吐司列表中取出第一个吐司
                Toast toast = toasterQueue.take();
                toast.butter();
                System.out.println(
                    "Butter : toast has buttered, id is " + toast.getId() + ", and status is: " + toast.getStatus());
                // 涂好黄油的吐司插入黄油列表中
                butterQueue.add(toast);
            }
        } catch (InterruptedException e) {
            System.out.println("Butter : " + "InterruptedException");
        }
    }
}

class Jammer implements Runnable {
    private final BlockingQueue<Toast> butterQueue;
    private final BlockingQueue<Toast> finishQueue;
    public Jammer(BlockingQueue<Toast> butterQueue, BlockingQueue<Toast> finishQueue) {
        this.butterQueue = butterQueue;
        this.finishQueue = finishQueue;
    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                TimeUnit.SECONDS.sleep(1);
                // 从涂好黄油的吐司列表中取出头部吐司
                Toast toast = butterQueue.take();
                toast.jam();
                System.out.println(
                    "Jammer : toast has jamed, id is " + toast.getId() + ", and status is: " + toast.getStatus());
                // 涂好果酱的吐司插入完成列表
                finishQueue.add(toast);
            }
        } catch (InterruptedException e) {
            System.out.println("Jammer : " + "InterruptedException");
        }
    }
}

class Consumer implements Runnable {
    private final BlockingQueue<Toast> finishQueue;
    private int consumerId;
    public Consumer(BlockingQueue<Toast> finishQueue) {
        this.finishQueue = finishQueue;
    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                TimeUnit.SECONDS.sleep(1);
                Toast toast = finishQueue.take();
                ++consumerId;
                System.out.println(
                    "Consumer : toast has consumed, id is " + toast.getId() + ", and status is: " + toast.getStatus());
                if (toast.getId() != consumerId) {
                    System.out.println("Consumer : has error, id is illegal, id is:" + toast.getId()
                        + ", and consumerId is:" + consumerId);
                }
            }
        } catch (InterruptedException e) {
            System.out.println("Consumer : " + "InterruptedException");
        }
    }
}

延迟队列:

  • 延迟队列是一种无界队列,用以存放实现了Delayed接口的对象(任务的执行时间依赖于设置的延迟时间是否到期)。
  • 延迟队列中任务的执行不依赖于任务的入队顺序/入队时间,而依赖于任务设定的延迟时间,只有任务延期时间到期时,任务才能消费并执行。
public class TestDelayQueue {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        Random random = new Random();
        List<DelayTask> taskList = new ArrayList<>();
        DelayQueue<DelayTask> taskQueue = new DelayQueue<>();
        for (int i = 0; i < 10; i++) {
            DelayTask delayTask = new DelayTask("task" + i, random.nextInt(100));
            taskList.add(delayTask);
            taskQueue.add(delayTask);
        }
        printQueueInfo(taskQueue, taskList);
        service.execute(new DelayTaskConsumer("consumer", taskQueue));
        service.shutdown();
    }

    public static void printQueueInfo(DelayQueue<DelayTask> queue, List<DelayTask> list) {
        System.out.println("Print List of DelayTask");
        list.forEach(System.out::println);
        System.out.println("\n" + "Print DelayQueue of DelayTask");
        // DelayQueue转换为Array时仅能保证全部包含,但不保证有序(iterator方法亦然)
        DelayTask[] tasks = queue.toArray(new DelayTask[0]);
        Arrays.stream(tasks).forEach(System.out::println);
    }
}

class DelayTaskConsumer implements Runnable {
    private final String name;
    private final BlockingQueue<DelayTask> tasksQueue;
    public DelayTaskConsumer(String name, BlockingQueue<DelayTask> queue) {
        this.name = name;
        this.tasksQueue = queue;
    }
    @Override
    public void run() {
        System.out.println(this.name + " has exec delayedTasks...");
        System.out.println(this.name + " delayed task size:" + tasksQueue.size());
        try {
            while (!tasksQueue.isEmpty()) {
                tasksQueue.take().run();
            }
            System.out.println("Delayed tasks has all completed...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 延时任务定义
 */
class DelayTask implements Delayed {
    private final String name;
    private final int delayTimeMillSecond;
    private final long expiredTime;
    public DelayTask(String taskName, int delayTimeMs) {
        this.delayTimeMillSecond = delayTimeMs;
        this.name = taskName;
        expiredTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(this.delayTimeMillSecond, TimeUnit.MILLISECONDS);
    }

    /**
     * 此方法非常重要,待getDelay方法返回值为0时,Delayed任务才会出队列,其中System.nanoTime()方法会一直变动。
     * 
     * @param unit 时间工具类
     * @return 延时时间
     */
    @Override
    public long getDelay(@NonNull TimeUnit unit) {
        return unit.convert(this.expiredTime - System.nanoTime(), TimeUnit.NANOSECONDS);
    }
    @Override
    public int compareTo(@NonNull Delayed o) {
        DelayTask that = (DelayTask) o;
        return this.delayTimeMillSecond - that.getDelayTimeMillSecond();
    }
    public void run() throws InterruptedException {
        System.out.println("Name: " + this.name + ", delayTime is:" + this.delayTimeMillSecond + " has begin...");
        TimeUnit.SECONDS.sleep(1);
    }
    public int getDelayTimeMillSecond() {
        return this.delayTimeMillSecond;
    }
    @Override
    public String toString() {
        return "DelayTask{" + "name='" + name + '\'' + ", delayTimeMillSecond=" + delayTimeMillSecond + '}';
    }
}

优先级队列:

  • 优先级队列(PriorityBlockingQueue)与延迟队列相似,但不是按照任务设置的延迟时间,而是按照任务的优先级顺序执行。

输入/输出管道:
在同步队列引入之前,线程通信更多时候采用管道概念,同步与阻塞由管道保证(类似与BloakingQueue)。Java中对应为PipedWrite类和PipedReader类。

public class TestPipeIo {
    public static void main(String[] args) throws InterruptedException {
        // channel for write and read
        PipedWriter writer = new PipedWriter();
        Sender sender = new Sender(writer);
        Receiver receiver = new Receiver(writer);
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.execute(sender);
        executorService.execute(receiver);
        TimeUnit.MINUTES.sleep(2);
        executorService.shutdownNow();
    }
}

/**
 * 消息发送类
 */
class Sender implements Runnable {
    // 定义一个管道写入类
    private PipedWriter writer;
    public Sender(PipedWriter writer) {
        this.writer = writer;
    }
    @Override
    public void run() {
        try {
            for (char c = 'a'; c <= 'z'; c++) {
                writer.write(c);
                System.out.println("Sender: char: " + c);
                TimeUnit.SECONDS.sleep(2);
            }
        } catch (IOException | InterruptedException e) {
            System.out.println("Sender IOException or InterruptedException...");
        }
    }
}

/**
 * 消息接收类
 */
class Receiver implements Runnable {
    // 定义一个管道读入类
    PipedReader reader;
    // 创建管道读入类必须与管道写入类关联起来
    public Receiver(PipedWriter writer) {
        try {
            this.reader = new PipedReader(writer);
        } catch (IOException e) {
            System.out.println("Receiver: IOException...");
        }
    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                char ch = (char) reader.read();
                System.out.println("Receiver: char: " + ch);
            }
        } catch (IOException e) {
            System.out.println("Receiver: IOException...");
        }
    }
}

21.3.5 重要构件类

并发编程、多线程交互中,Java已提供了非常便利的实现类。

21.3.5.1 CountDown相关类

CountDownLatch类:

  • CountDownLatch类用于同步一个或者多个任务,目的在于让其他任务等待标记为CountDownLatch的任务执行结束。*
  • 可以向CountDownLatch对象设置一个初始计数值,任何在这个对象上调用wait()方法的任务均需要等待,直至此计数值为0.*
    使用方法如下:
public class TestCountDown {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        // CountDownLatch公共变量
        CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 10; i++) {
            service.execute(new TaskB(countDownLatch, i));
        }
        for (int j = 0; j < 100; j++) {
            service.execute(new TaskA(countDownLatch, j));
        }
        service.shutdown();
    }
}

class TaskA implements Runnable {
    private int id = 0;
    private CountDownLatch downLatch;
    public TaskA(CountDownLatch latch, int id) {
        this.downLatch = latch;
        this.id = id;
    }
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("TaskA: " + id + " completed...");
            // CountDownLatch变量减1操作,当变量downLatch的初始值为0时,调用wait()方法的任务才能执行
            downLatch.countDown();
        } catch (InterruptedException e) {
            System.out.println("TaskA: " + id + " InterruptedException is:" + e.getMessage());
        }
    }
}

class TaskB implements Runnable {
    private int id;
    private CountDownLatch downLatch;
    public TaskB(CountDownLatch latch, int id) {
        this.id = id;
        this.downLatch = latch;
    }
    @Override
    public void run() {
        try {
            // CountDownLatch变量等待至0
            downLatch.await();
            System.out.println("TaskB: " + id + " has begin...");
        } catch (InterruptedException e) {
            System.out.println("TaskB: " + id + " InterruptedException is:" + e.getMessage());
        }
    }
}

CycliBarrier类:

  • CycliBarrier工具类与CountDownLatch类似,有一个类似于“栈栏”的概念,当多个线程同时进入“栈栏”时之后(因各个线程开始时间与执行时间不一致),多个单独的线程同时往下走。
public class TestCircleBarrier {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        int horseSize = 10;
        int finishLine = 75;
        ExecutorService service = Executors.newCachedThreadPool();
        List<Horse> horses = new ArrayList<>();
        for (int i = 0; i < horseSize; i++) {
            Horse horse = new Horse(i);
            horses.add(horse);
        }
        // horseSize+1代表HorseRace任务也作为栈栏任务,保证所有任务同时准备好之后,再执行下述定义任务
        CyclicBarrier barrier = new CyclicBarrier(horseSize + 1, () -> System.out.println("Horse Race begin..."));
        // 提交赛马任务
        for (Horse horse : horses) {
            horse.setBarrier(barrier);
            service.execute(horse);
        }
        // 提交赛马统计任务,用以统计胜利者
        new Thread(new HorseRace(horses, service, barrier, finishLine)).start();
    }
}

class HorseRace implements Runnable {
    private final int finishLine;
    private final List<Horse> horses;
    private final ExecutorService service;
    private final CyclicBarrier barrier;
    HorseRace(List<Horse> horses, ExecutorService service, CyclicBarrier barrier, int finishLine) {
        this.finishLine = finishLine;
        this.horses = horses;
        this.service = service;
        this.barrier = barrier;
    }

    @Override
    public void run() {
        try {
            // 待所有赛马均已准备好,且已运行了barrier的自定义程序后,再执行下述统计过程
            barrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            System.out.println("HourseRace has exception: " + e.getMessage());
        }
        while (!Thread.interrupted()) {
            try {
                for (Horse horse : horses) {
                    System.out.println("Horse " + horse.getId() + " tracks:" + horse.getRecords());
                    if (horse.getStride() >= this.finishLine) {
                        System.out.println("Horse " + horse.getId() + " won");
                        service.shutdownNow();
                        return;
                    }
                }
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                System.out.println("HourseRace has exception: " + e.getMessage());
            }
        }
    }
}

class Horse implements Runnable {
    private final StringBuilder recordStride;
    private int id = 0;
    private CyclicBarrier barrier;
    private int stride;
    private Random rand = new Random();
    public Horse(int id) {
        this.id = id;
        this.recordStride = new StringBuilder();
    }
    public void setBarrier(CyclicBarrier barrier) {
        this.barrier = barrier;
    }
    @Override
    public void run() {
        try {
            this.recordStride.append(0);
            System.out.println("Horse " + id + " has begin, and stride: " + stride);
            TimeUnit.SECONDS.sleep(rand.nextInt(5));
            // 待所有赛马准备好,处于同一个起跑线时
            barrier.await();
            while (!Thread.interrupted()) {
                this.stride += rand.nextInt(3);
                this.recordStride.append(" ").append(stride);
                TimeUnit.MILLISECONDS.sleep(rand.nextInt(200));
            }
        } catch (InterruptedException | BrokenBarrierException e) {
            System.out.println("Horse " + id + " has exception: " + e.getMessage());
        }
    }
    public String getRecords() {
        return recordStride.toString();
    }
    public int getStride() {
        return this.stride;
    }
    public int getId() {
        return this.id;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值