线程基础

1.Java 线程的基本概念

在操作系统中两个比较容易混淆的概念是进程(Process)和线程(Thread)。操作系统中的进程是资源的组织单位。进程有一个包含了程序内容和数据的地址空间,以及其他的资源,包括打开的文件、子进程和信号处理器等。不同进程的地址空间是相互隔离的。而线程表示的是程序的执行流程,是CPU调度的基本单位。线程有自己的程序计数器、寄存器、栈和帧等引入线程的动机在于操作系统中阻塞式I/O的存在。当一个线程所执行的I/O被阻塞的时候,同一个进程的其他线程可以使用CPU来进行计算。这样的话,就提高了应用的执行效率。线程的概念在主流的操作系统和编程语言中都得到了支持。(infoQ:Java深度历险)。

2.线程的状态

线程状态

要获取线程的状态,可以通过Thread.currentThread.getState()来获取。返回的是一个枚举类型,是Thread内部的一个枚举。java本身层面的状态包含: NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;

(1)NEW: 意思是这个线程没有被start()启动,或者说还根本不是一个真正意义上的线程,从本质上讲这只是创建了一个Java外壳,还没有真正的线程来运行。

注意:调用了start()并不代表状态就立即改变,中间还有一些步骤,如果在这个启动的过程中有另一个线程来获取它的状态,其实是不正确的,要看哪些中间步骤是否已经完成了。

(2)RUNNABLE:当处于NEW状态的线程发生start()结束后线程就将变成RUNNABLE状态。获取状态都是获取其他线程的状态,而不是自己的状态。为什么?程序正在运行中的线程就肯定处于RUNNABLE状态。比如通过Thread.currentThread().getState()来获取当前线程中的状态,只会得到“RUNNABLE”,而不会得到其他的值。

RUNNABLE状态也可以理解为存活着正在尝试征用CPU的线程.由于在真正的系统中,并不是开启一个线程后,CPU就只为这一个线程服务,它必须使用许多调度算法来达到某种平衡,不过这个时候线程依然处于RUNNABLE状态。

举个列子:当某个运动中的线程发生了 yield()操作时,其实看到的线程状态也是RUNNABLE,只是它有一个细节的内部变化,就是做一个简单的让步如果一个线程可能做大量的CPU计算,认为自己会在相对较长的时间内占用资源,如果调度算法有问题,就会一直占用CPU,所以在适当的时候做下让步,让别人也来使用下CPU资源

(3)BLOCKED:阻塞状态,或者说线程已经被挂起。进入synchronized方法或者IO阻塞。

一旦线程处于阻塞状态,线程就像真的什么也不做一样,在Java层面始终无法唤醒它。许多人说现在用interrupt()方法来唤醒它,一点用处也没有,因为interrupt()只是在里面做一个标记而已,不会真正唤醒处于阻塞状态的线程。

(4)WAITING:这种状态通常是指一个线程拥有对象锁后进入到相应的代码区域后,调用相应的“锁对象”的wait()方法操作后产生的一种结果。变相的实现还有LockSupport.park(),Thread.join()等,他们也是在等待另一个对象事件的发生,也就是描述了等待的意思。

上边的BLOCKED 状态也是等待的意思,它们有什么关系与区别呢?

BLOCKED 是虚拟机 认为程序还不能进入到某个区域,因为同时进去就会有问题,这是一块临界区。 发生wait()操作的先决条件是要进入到临界区,也就是线程已经拿到了“门票”,自己可能进去做了一些事情,但此时通过判定某些业务上的参数(由具体业务决定),发现还有一些其他配合的资源没有准备充分,那么自己就等等再做其他的事情。

典型的就是生产者和消费者,生产者消费过快仓库满了,而消费者还没有把东西拿走时,生产者就有空位在做其他事情。

这种状态下,如果发生了对该线程的interrupt()是有用的,处于该状态的线程内部会抛出一个InterruptedException 异常,这个异常会在run()方法里捕获,使得run()方法正常地执行完成。当然在run()方法内部捕获异常后,还可以让线程继续运行,根据具体的应用场景来决定的。

Object.wait()是否需要死等呢? 不是,除了中断外,它还有两个重构方法:

  • Object.wait(int timeout),传入的timeout参数是超时的毫秒值,超过这个值后会自动唤醒,继续做下面的操作。

  • Object.wait(int timeout, int nanos),这是一个更精确的超时设置,理论上可以精确到纳秒。

同样的,LockSupport.park()、Thread.join()这些方法都会有类似的重构方法来设置超时,此时的状态不再是WAITING,而是TIMED_WAITING.

(5)TIMED_WAITING状态: Thread.sleep()和上边的方式都可以进入TIMED_WAITING状态。 可以这样理解:当调用 Thread.sleep()方法时,相当于使用某个时间资源作为锁对象,进而达到等待的目的,当时间达到时触发线程回到工作状态。 (6)TERMINATED状态: 线程结束,run()方法走完了,线程就处于这种状态。

线程安全

1.并发内存模型

细说java多线程之内存可见性

2.volatile

通过较低层次的锁(JVM会向处理器发送一条LOCK指令)实现内存屏障,保证可见性。 JDK1.7并发包里新增一个队列集合类LinkedTranQueue,在使用Volatile变量时,用一种追加字节的方式来优化队列出队和入队的性能。

3.final

在JMM中要求final域(属性)的初始化动作必须在构造方法return之前完成。换言之,一个对象创建以及赋值给一个引用是两个动作,对象创建还需要经历分配空间和属性初始化的过程,普通的属性初始化允许发生在构造方法 return之后(指令重排序).

class FinalFieldExample {
  final int x;
  int y;
  static FinalFieldExample f;
  public FinalFieldExample() {
    x = 3;
    y = 4;
  }

  static void writer() {
    f = new FinalFieldExample();
  }

  static void reader() {
    if (f != null) {
      int i = f.x;
      int j = f.y;
    }
  }
}

上边的代码并不能保证j = 4,因为不是final修饰的。

public FinalFieldExample() { // bad!
  x = 3;
  y = 4;
  // bad construction - allowing this to escape  逃逸问题
  global.obj = this;
}

那么,从global.obj中读取this的引用线程不会保证读取到的x的值为3。
另外,如果final 所修饰的不是普通变量,而是数组、对象,那么它能保证自己本身的初始化,但是它作为对象,对内部的属性是无法保证的。

4.栈封闭

栈封闭算是一种概念,也就是线程操作的数据都是私有的,不会与其他的线程共享数据。简单来说,如果每个线程所访问的JVM区域是隔离的,那么这个系统就像一个单线程系统一样简单了,叫做栈 封闭。

一个是 使用局部变量,另一个就是 ThreadLocal.

5.ThreadLocal

ThreadLocal可以放一个线程级别的变量,但是它本身可以被多个线程共享使用,而且又可以达到线程安全的目的,且绝对线程安全。

数据库事务前提必须保证是用一个连接,当我们不用框架、不用连接池,使用原生javaWeb来操作时,connection怎么管理呢?

获取连接放入到ThreadLocal中,关闭连接时remove掉。

Spring使用Aop切入业务代码,会根据对应的事物管理器提取出相应的事物对象,DataSourceTransactionManager,就会从DataSource中获取一个连接对象,通过一定的包装后将其保存在ThreadLocal中。并且Spring重写了其中的getConnecttion(),该方法的返回将由Spring来控制,这样Spring就能让线程内多次获取到的Connection对象是同一个。

不是说共享变量无法做到线程私有吗?ThreadLocal是怎么做到的?

原子性和锁

1.synchronized

Java中的每一个对象都可以作为锁,悲观锁。

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块, 锁是synchronized括号里配置的对象。 synchronized的开销比较大,JDK1.8做了很多优化,基本和锁差不多了,推荐使用synchronized(道听途说的)。

2.什么是乐观锁

悲观锁意味着必须发生锁操作,不论多个线程会不会发生冲突都必须加锁。悲观锁将会产生非常多的指令开销来用以同步,甚至由于锁粒度的使用不当导致不应该锁住的代码被锁住了。而实际在系统中,细化锁粒度后,即使并发量很大,同一个临界区的征用概率也不会太大。

现在CPU基本都会提供一种CAS指令机制,这种机制就是先获取Old值,然后计算出New值,基于可见性的变量检测是否被修改,若没有修改,则回写到主内存表示成功。这个步骤大概几条指令就可以完成,如果发生征用则只有一个线程能成功,与悲观锁相比,绝大部分情况下没有那么大开销。

这种CAS机制子在java中应用到两个抽象层次: 一是应用到JVM级别本身的synchronized锁里面;二是应用到基于Java语言本身这个抽象层次上面,提供一种宏观上的锁机制,在某些时候可以达到更为细粒度的原子处理,这种CAS应用十分广泛,尤其是基于它来做自旋。

3.Atomic

Atomic 原子变量可以分为4种类型:

基本变量操作: 分别对Boolean、Integer、Long进行操作,对应的类名为AtomicBoolean、AtomicInteger和AtomicLong
引用(reference) 操作:也就是对象引用的操作,可以做到原子级别,当多个线程对同一个引用发生修改时,只会有一个成功。对于这种操作还存在ABA问题,所以会有多个类处理引用的原子操作,如:AtomicReference, AtomicStampedRerence, AtomicMarkableReference
数组操作:操作的不是数组,而是数组中的每一个元素。针对每一个元素的操作都是原子的。AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
Updater: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater

4.Lock

读写锁 ReadWriteLock

基本操作

Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也是一个对象。两个线程执行的代码要实现同步互斥的效果,他们必须用同一个Lock对象。锁是在代表要操作的资源的类的内部方法中,而不是在线程代码中,想想上厕所。

读写锁: 分为读锁和写锁,多个读锁不互斥,读写锁互斥,写锁与写锁互斥,这是由JVM自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人呢在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁。

package com.ddyblackhat.javase.thread;

import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁测试
 * 读与读不互斥, 读与写互斥,写与写互斥
 * @author dudy
 *
 */
public class ReadWriteLockTest {

    public static void main(String[] args) {
        final Queque3 q = new Queque3();

        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    q.get();
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    q.set(new Random().nextInt(100));
                }
            }).start();
        }

//        Thread-0 be ready to read data!
//        Thread-0 have get the data :0
//      写与写,读与写互斥
//        Thread-1 be ready to set data
//        Thread-1 have set the data :46
//        Thread-3 be ready to set data
//        Thread-3 have set the data :92
//        Thread-5 be ready to set data
//        Thread-5 have set the data :60
//      读与读不互斥
//        Thread-4 be ready to read data!
//        Thread-2 be ready to read data!
//        Thread-2 have get the data :60
//        Thread-4 have get the data :60

    }

}

// 操作的资源对象类
class Queque3 {

    private int data;
    // 获取读写锁对象
    ReadWriteLock rw = new ReentrantReadWriteLock();
    public int get() {
        try {
            // 读锁
            rw.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " be ready to read data!");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " have get the data :" + data);
            return this.data;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rw.readLock().unlock();
        }
        return 0;
    }

    public void set(int data) {
        try {
            // 写锁
            rw.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + " be ready to set data");
            Thread.sleep(1000);
            this.data = data;
            System.out.println(Thread.currentThread().getName() + " have set the data :" + data);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            rw.writeLock().unlock();
        }
    }

}

简单缓存系统

Hibernate中session.get() 和 load(),load代理缓存,只不过是缓存的是一个对象。

//private Map<String, Object> cache = new ConcurrentHashMap<String, Object>();
private Map<String, Object> cache = new HashMap<String, Object>();
volatile boolean cacheValid = false;
volatile Object data = null;

ReadWriteLock rw = new ReentrantReadWriteLock();

public Object getDataByRWLock(String key) { // data 要内存保证可见性
    rw.readLock().lock();
    try {
        data = cache.get(key);
        //if (!cacheValid) {
        if (data == null) {
            // 这里要写入数据,所以要先释放掉读锁,上写锁
            rw.readLock().unlock();
            rw.writeLock().lock(); // ①
            try {
                //if (!cacheValid) { // 再一次判断,因为可能多个线程同时进入执行到①
                if (data == null) {
                    data = new Random().nextInt(1000);
                    cache.put(key, data);
                    cacheValid = true;
                    System.out.println("data has write : " + data);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                rw.writeLock().unlock();
            }
            rw.readLock().lock();
        }

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        rw.readLock().unlock();
    }
    return data;
}

Q:注意点

  • 在同一个线程中,持有读锁后,不能直接调用写锁的lock方法。会造成死锁,通被称为读锁不可升级。先读锁unlock后再写锁lock。

  • 在同一个线程中,持有写锁后,可调用读锁的lock方法,在此之后如果调用写锁的unlock方法,那么当前锁将降级为读锁。

API 文档中: 在 writer 释放写入锁时,reader 和 writer 都处于等待状态,在这时要确定是授予读取锁还是授予写入锁。Writer 优先比较普遍,因为预期写入所需的时间较短并且不那么频繁。Reader 优先不太普遍,因为如果 reader 正如预期的那样频繁和持久,那么它将导致对于写入操作来说较长的时延。公平或者“按次序”实现也是有可能的。
在 reader 处于活动状态而 writer 处于等待状态时,确定是否向请求读取锁的 reader 授予读取锁。Reader 优先会无限期地延迟 writer,而 writer 优先会减少可能的并发。
确定是否重新进入锁:可以使用带有写入锁的线程重新获取它吗?可以在保持写入锁的同时获取读取锁吗?可以重新进入写入锁本身吗?
可以将写入锁在不允许其他 writer 干涉的情况下降级为读取锁吗?可以优先于其他等待的 reader 或 writer 将读取锁升级为写入锁吗? 当评估给定实现是否适合您的应用程序时,应该考虑所有这些情况。

condition

synchronized与wait()和notify()/notifyAll()方法结合可以实现等待/通知模型,ReentrantLock同样可以,但是需要借助Condition,且Condition有更好的灵活性,具体体现在: 1.一个Lock 里边可以创建多个Condition实例,实现多路通知 2.notify()方法进行通知时,被通知的线程时Java虚拟机随机选择的,但是ReentranLock结合Condition可以实现有选择性的通知,这是非常重要的。

看下边的ABC打印输出的例子,这个例子感觉不好。

/**
 * java contitionTest 使用 Condition实现 传统的例子, 一个数 +1 -1,循环5次
 *                                       面试 ABC打印输出的例子
 * @author dudy
 *
 */
public class ConditionTest {

    public static void main(String[] args) {
        final ABCPrint p = new ABCPrint();

        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                for (int i = 0; i < 5; i++) {
                    p.printA();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                for (int i = 0; i < 5; i++) {
                    p.printB();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                for (int i = 0; i < 5; i++) {
                    p.printC();
                }
            }
        }).start();

    }

}

class ABCPrint {
    private Lock lock = new ReentrantLock();

    private int state = 0;
    Condition conditonA = lock.newCondition();
    Condition conditonB = lock.newCondition();
    Condition conditonC = lock.newCondition();

    public void printA() {
        lock.lock();
        try {
            while(state != 0){// 如果state != 0 ,当前线程就等待。
                conditonA.await();
            }
            System.out.println("A");
            state = 1;
            conditonB.signal(); // 通知特定的线程来执行
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try {
            while(state != 1){
                conditonB.await();
            }
            System.out.println("B");
            state = 2;
            conditonC.signal();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try {
            while(state != 2){
                conditonC.await();
            }
            System.out.println("C");
            System.out.println("---------------------------");
            state = 0;
            conditonA.signal();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

API中有一个 阻塞队列的例子,ArrayBlockingQueue类似的功能。

6.并发编程核心AQS原理

7.锁的自身优化方法

常见并发编程工具

Semaphore & CyclicBarrier & CountDownLatch

Semaphore 信号量

一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。 类似于 10个人上厕所,只有3个坑,走了一个人之后下一个人才可以。

SemaphoreTest.java

public class SemaphoreTest {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        final Semaphore sp = new Semaphore(3,true);
        for (int i = 0; i < 10; i++) {
            Runnable runnable = new Runnable() {
                public void run() {
                    try {
                        sp.acquire();
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    System.out.println(
                            "线程" + Thread.currentThread().getName() + "进入,当前已有" + (3 - sp.availablePermits()) + "个并发");
                    try {
                        Thread.sleep((long) (Math.random() * 10000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程" + Thread.currentThread().getName() + "即将离开");
                    sp.release();
                    // 下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
                    System.out.println(
                            "线程" + Thread.currentThread().getName() + "已离开,当前已有" + (3 - sp.availablePermits()) + "个并发");
                }
            };
            service.execute(runnable);
        }

        service.shutdown();
    }

}

执行结果

线程pool-1-thread-1进入,当前已有3个并发
线程pool-1-thread-2进入,当前已有3个并发
线程pool-1-thread-3进入,当前已有3个并发
线程pool-1-thread-1即将离开
线程pool-1-thread-1已离开,当前已有2个并发
线程pool-1-thread-4进入,当前已有3个并发
线程pool-1-thread-2即将离开
线程pool-1-thread-2已离开,当前已有2个并发
线程pool-1-thread-5进入,当前已有3个并发
线程pool-1-thread-3即将离开
线程pool-1-thread-3已离开,当前已有2个并发
线程pool-1-thread-7进入,当前已有3个并发
线程pool-1-thread-4即将离开
线程pool-1-thread-4已离开,当前已有2个并发
线程pool-1-thread-9进入,当前已有3个并发
线程pool-1-thread-7即将离开
线程pool-1-thread-7已离开,当前已有2个并发
线程pool-1-thread-6进入,当前已有3个并发
线程pool-1-thread-5即将离开
线程pool-1-thread-5已离开,当前已有2个并发
线程pool-1-thread-10进入,当前已有3个并发
线程pool-1-thread-9即将离开
线程pool-1-thread-9已离开,当前已有2个并发
线程pool-1-thread-8进入,当前已有3个并发
线程pool-1-thread-6即将离开
线程pool-1-thread-6已离开,当前已有2个并发
线程pool-1-thread-8即将离开
线程pool-1-thread-8已离开,当前已有1个并发
线程pool-1-thread-10即将离开
线程pool-1-thread-10已离开,当前已有0个并发

CyclicBarrier

CyclicBarrier(循环路障):维护一个计数器,等待这个CyclicBarrier的线程必须等到计数器达到某个值时,才可以继续。好比整个公司的人员利用周末时间集体去郊游一样,先各自从家出发到公司集合,再同时出发到公园游玩,在指定地点集合后再同时开始就餐。

public class CyclicBarrierTest {

    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        final  CyclicBarrier cb = new CyclicBarrier(3);
        for(int i=0;i<3;i++){
            Runnable runnable = new Runnable(){
                    public void run(){
                    try {
                        Thread.sleep((long)(Math.random()*10000));    
                        System.out.println("线程" + Thread.currentThread().getName() + 
                                "即将到达集合地点1,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));                        
                        cb.await();

                        Thread.sleep((long)(Math.random()*10000));    
                        System.out.println("线程" + Thread.currentThread().getName() + 
                                "即将到达集合地点2,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));
                        cb.await();    
                        Thread.sleep((long)(Math.random()*10000));    
                        System.out.println("线程" + Thread.currentThread().getName() + 
                                "即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));                        
                        cb.await();                        
                    } catch (Exception e) {
                        e.printStackTrace();
                    }                
                }
            };
            service.execute(runnable);
        }
        service.shutdown();
    }
}

运行结果:

线程pool-1-thread-2即将到达集合地点1,当前已有1个已经到达,正在等候
线程pool-1-thread-3即将到达集合地点1,当前已有2个已经到达,正在等候
线程pool-1-thread-1即将到达集合地点1,当前已有3个已经到达,都到齐了,继续走啊
线程pool-1-thread-1即将到达集合地点2,当前已有1个已经到达,正在等候
线程pool-1-thread-3即将到达集合地点2,当前已有2个已经到达,正在等候
线程pool-1-thread-2即将到达集合地点2,当前已有3个已经到达,都到齐了,继续走啊
线程pool-1-thread-2即将到达集合地点3,当前已有1个已经到达,正在等候
线程pool-1-thread-1即将到达集合地点3,当前已有2个已经到达,正在等候
线程pool-1-thread-3即将到达集合地点3,当前已有3个已经到达,都到齐了,继续走啊

CountDownLatch

一个或者是一部分线程 ,等待另外一部线程都完成了,再继续执行

/**
 *  一个或者是一部分线程 ,等待另外一部线程都完成了,再继续执行
 * @author dudy
 *
 */
public class CountdownLatchTest {

    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        final CountDownLatch cdOrder = new CountDownLatch(1);
        final CountDownLatch cdAnswer = new CountDownLatch(3);        
        for(int i=0;i<3;i++){
            Runnable runnable = new Runnable(){
                    public void run(){
                    try {
                        System.out.println("线程" + Thread.currentThread().getName() + 
                                "正准备接受命令");                        
                        cdOrder.await();
                        System.out.println("线程" + Thread.currentThread().getName() + 
                        "已接受命令");                                
                        Thread.sleep((long)(Math.random()*10000));    
                        System.out.println("线程" + Thread.currentThread().getName() + 
                                "回应命令处理结果");                        
                        cdAnswer.countDown();                        
                    } catch (Exception e) {
                        e.printStackTrace();
                    }                
                }
            };
            service.execute(runnable);
        }        
        try {
            Thread.sleep((long)(Math.random()*10000));

            System.out.println("线程" + Thread.currentThread().getName() + 
                    "即将发布命令");                        
            cdOrder.countDown();
            System.out.println("线程" + Thread.currentThread().getName() + 
            "已发送命令,正在等待结果");    
            cdAnswer.await();
            System.out.println("线程" + Thread.currentThread().getName() + 
            "已收到所有响应结果");    
        } catch (Exception e) {
            e.printStackTrace();
        }                
        service.shutdown();

    }
}

参考:

张孝祥张老师多线程视频。

Java特种兵第5章。

并发编程网