JavaSE(7续b)面试

九、Java 的多线程和并发库

对于 Java 程序员来说,多线程在工作中的使用场景还是比较常见的,而仅仅掌握了 Java 中的传统多线程机制,
还是不够的。在 JDK5.0 之后,Java 增加的并发库中提供了很多优秀的 API,在实际开发中用的比较多。因此在看具体
的面试题之前我们有必要对这部分知识做一个全面的了解。
(一)多线程基础知识–传统线程机制的回顾
( 1 ) 传统使用类 Thread 和接口 Runnable 实现
1.在 Thread 子类覆盖的 run 方法中编写运行代码
方式一

new Thread(){
@Override
public void run(){
while(true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} }
}
}.start();

2.在传递给 Thread 对象的 Runnable 对象的 run 方法中编写代码

**new Thread(new Runnable(){
public void run(){
while(true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
} }
}).start();**

3.总结
查看 Thread 类的 run()方法的源代码,可以看到其实这两种方式都是在调用 Thread 对象的 run 方法,如果 Thread类的 run 方法没有被覆盖,并且为该 Thread 对象设置了一个 Runnable 对象,该 run 方法会调用 Runnable 对象的run 方法

/**
* If this thread was constructed using a separate
* Runnable run object, then that
* Runnable object's run method is called;
* otherwise, this method does nothing and returns.
* 

* Subclasses of Thread should override this method. * * @see #start() * @see #stop() * @see #Thread(ThreadGroup, Runnable, String) */ @Override public void run() { if (target != null) { target.run(); } }

( 2 ) 定实现时器 Timer 和 TimerTask
Timer 在实际开发中应用场景不多,一般来说都会用其他第三方库来实现。但有时会在一些面试题中出现。
下面我们就针对一道面试题来使用 Timer 定时类。
1.请模拟写出双重定时器(面试题)
要求:使用定时器,间隔 4 秒执行一次,再间隔 2 秒执行一次,以此类推执行。

class TimerTastCus extends TimerTask{
@Override
public void run() {
count = (count +1)%2;
System.err.println("Boob boom ");
new Timer().schedule(new TimerTastCus(), 2000+2000*count);
}
} 
Timer timer = new Timer();
timer.schedule(new TimerTastCus(), 2000+2000*count);
while (true) {
System.out.println(new Date().getSeconds());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
//PS:下面的代码中的 count 变量中
//此参数要使用在你匿名内部类中,使用 final 修饰就无法对其值进行修改,
//只能改为静态变量
private static volatile int count = 0;

( 3 ) 线程互斥与同步
在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源时,如多个线程急用同一台打印机,会使打印结果交织在一起,难于区分。当多个线程急用共享变量,表格,链表时,可能会导致数据处理出错,因此线程同步的主要任务是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序的执行具有可再现性。
当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。
**1. 间接相互制约。**一个系统中的多个线程必然要共享某种系统资源,如共享 CPU,共享 I/O 设备,所谓间接相
互制约即源于这种资源共享,打印机就是最好的例子,线程 A 在使用打印机时,其它线程都要等待。
**2. 直接相互制约。**这种制约主要是因为线程之间的合作,如有线程 A 将计算结果提供给线程 B 作进一步处理,那么线程 B 在线程 A 将数据送达之前都将处于阻塞状态。
间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程 A 和线程 B 互斥访问某个资源则它们之间就会产个顺序问题——要么线程 A 等待线程 B 操作完毕,要么线程 B 等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步。
下面我们通过一道面试题来体会线程的交互。
要求:子线程运行执行 10 次后,主线程再运行 5 次。这样交替执行三遍

public static void main(String[] args) {
final Bussiness bussiness = new Bussiness();
//子线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
bussiness.subMethod();
} }
}).start();
//主线程
for (int i = 0; i < 3; i++) {
bussiness.mainMethod();
} } }
class Bussiness {private boolean subFlag = true;
public synchronized void mainMethod() {
while (subFlag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
} }
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()
+ " : main thread running loop count -- " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} }
subFlag = true;
notify();
}
public synchronized void subMethod() {
while (!subFlag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
} }
for (int i = 0; i < 10; i++) {
System.err.println(Thread.currentThread().getName()
+ " : sub thread running loop count -- " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} }
subFlag = false;
notify();
} }

( 4 ) 线程局部变量 ThreadLocal
 ThreadLocal 的作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。
 每个线程调用全局 ThreadLocal 对象的 set 方法,在 set 方法中,首先根据当前线程获取当前线程的ThreadLocalMap 对象,然后往这个 map 中插入一条记录,key 其实是 ThreadLocal 对象,value 是各自的 set
方法传进去的值。也就是每个线程其实都有一份自己独享的 ThreadLocalMap对象,该对象的 Key 是 ThreadLocal对象,值是用户设置的具体值。在线程结束时可以调用 ThreadLocal.remove()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的 ThreadLocal 变量。
 ThreadLocal 的应用场景:
➢ 订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。
➢ 银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。
➢ 例如 Strut2 的 ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext 方法拿到的对象都不相同,对同一个线程来说,不管调用 getContext 方法多少次和在哪个模块中 getContext 方法,拿到的都是同一个。
1. ThreadLocal 的使用方式
(1) 在关联数据类中创建 private static ThreadLocal在下面的类中,私有静态 ThreadLocal 实例(serialNum)为调用该类的静态 SerialNum.get() 方法的每个线程维护了一个“序列号”,该方法将返回当前线程的序列号。(线程的序列号是在第一次调用 SerialNum.get() 时
分配的,并在后续调用中不会更改。)

public class SerialNum {
// The next serial number to be assigned
private static int nextSerialNum = 0;
private static ThreadLocal serialNum = new ThreadLocal() {
protected synchronized Object initialValue() {
return new Integer(nextSerialNum++);
}
};
public static int get() {
return ((Integer) (serialNum.get())).intValue();
} }
另一个例子,也是私有静态 ThreadLocal 实例:
public class ThreadContext {
private String userId;
private Long transactionId;
private static ThreadLocal threadLocal = new ThreadLocal(){
@Override
protected ThreadContext initialValue() {
return new ThreadContext();
}
};
public static ThreadContext get() {
return threadLocal.get();
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Long getTransactionId() {
return transactionId;
}
public void setTransactionId(Long transactionId) {
this.transactionId = transactionId;
} }

补充:在 JDK 的 API 对 ThreadLocal 私有化的说明。并举例‘线程唯一标识符’
UniqueThreadIdGenerator ,大家学习是可以结合官方 API 来学习。
2. 在 Util 类中创建 ThreadLocal
这是上面用法的扩展,即把 ThreadLocal 的创建放到工具类中。

public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory; //定义 SessionFactory
static {
try {
// 通过默认配置文件 hibernate.cfg.xml 创建 SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化 SessionFactory 失败!", ex);
throw new ExceptionInInitializerError(ex);
} }
//创建线程局部变量 session,用来保存 Hibernate 的 Session
public static final ThreadLocal session = new ThreadLocal();
/**
* 获取当前线程中的 Session
* @return Session
* @throws HibernateException
*/
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// 如果 Session 还没有打开,则新开一个 Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //将新开的 Session 保存到线程局部变量中
}
return s;
}
public static void closeSession() throws HibernateException {
//获取线程局部变量,并强制转换为 Session 类型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
} }

3. 在 Runnable 中创建 ThreadLocal
在线程类内部创建 ThreadLocal,基本步骤如下:
①、在多线程的类(如 ThreadDemo 类)中,创建一个 ThreadLocal 对象 threadXxx,用来保存线程间需要隔离处理的对象 xxx。
②、在 ThreadDemo 类中,创建一个获取要隔离访问的数据的方法 getXxx(),在方法中判断,若ThreadLocal 对象为 null 时候,应该 new()一个隔离访问类型的对象,并强制转换为要应用的类型
③、在 ThreadDemo 类的 run()方法中,通过调用 getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。

public class ThreadLocalTest implements Runnable{
ThreadLocal studenThreadLocal = new ThreadLocal();
@Override
public void run() {
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " is running...");
Random random = new Random();
int age = random.nextInt(100);
System.out.println(currentThreadName + " is set age: " + age);
Studen studen = getStudent(); //通过这个方法,为每个线程都独立的 new 一个 student 对象,每个线程的的
student 对象都可以设置不同的值
studen.setAge(age);
System.out.println(currentThreadName + " is first get age: " + studen.getAge());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( currentThreadName + " is second get age: " + studen.getAge());
}
private Studen getStudent() {
Studen studen = studenThreadLocal.get();
if (null == studen) {
studen = new Studen();
studenThreadLocal.set(studen);
}
return studen;
}
public static void main(String[] args) {
ThreadLocalTest t = new ThreadLocalTest();
Thread t1 = new Thread(t,"Thread A");
Thread t2 = new Thread(t,"Thread B");
t1.start();
t2.start();
} }
class Studen{
int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

( 5 ) 多线程共享数据
在 Java 传统线程机制中的共享数据方式,大致可以简单分两种情况:
➢ 多个线程行为一致,共同操作一个数据源。也就是每个线程执行的代码相同,可以使用同一个 Runnable 对象,这个 Runnable 对象中有那个共享数据,例如,卖票系统就可以这么做。
➢ 多个线程行为不一致,共同操作一个数据源。也就是每个线程执行的代码不同,这时候需要用不同的Runnable 对象。例如,银行存取款。
下面我们通过两个示例代码来分别说明这两种方式。
1. 多个线程行为一致共同操作一个数据
如果每个线程执行的代码相同,可以使用同一个 Runnable 对象,这个 Runnable 对象中有那个共享数据,例如,买票系统就可以这么做。

/**
*共享数据类
/ class ShareData{ private int num = 10 ; public synchronized void inc() { num++; System.out.println(Thread.currentThread().getName()+":
invoke inc method num =" + num); try { Thread.sleep(1000); } catch
(InterruptedException e) { e.printStackTrace(); } } } /

*多线程类
/ class RunnableCusToInc implements Runnable{ private ShareData shareData; public RunnableCusToInc(ShareData data) { this.shareData =
data; } @Override public void run() { for (int i = 0; i < 5; i++) {
shareData.inc(); } } } /

*测试方法
**/ public static void main(String[] args) { ShareData shareData = new ShareData(); for (int i = 0; i < 4; i++) { new Thread(new
RunnableCusToInc(shareData),"Thread "+ i).start(); } }}

2. 多个线程行为不一致共同操作一个数据

如果每个线程执行的代码不同,这时候需要用不同的 Runnable 对象,有如下两种方式来实现这些 Runnable 对象之间的数据共享:

  1. 将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个 Runnable 对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

    public static void main(String[] args) {
    ShareData shareData = new ShareData();
    for (int i = 0; i < 4; i++) {
    if(i%2 == 0){
    new Thread(new RunnableCusToInc(shareData),"Thread "+ i).start();
    }else{
    new Thread(new RunnableCusToDec(shareData),“Thread “+ i).start();
    }
    } }
    //封装共享数据类
    class RunnableCusToInc implements Runnable{
    //封装共享数据
    private ShareData shareData;
    public RunnableCusToInc(ShareData data) {
    this.shareData = data;
    }
    @Override
    public void run() {
    for (int i = 0; i < 5; i++) {
    shareData.inc();
    } } }
    //封装共享数据类
    class RunnableCusToDec implements Runnable{
    //封装共享数据
    private ShareData shareData;
    public RunnableCusToDec(ShareData data) {
    this.shareData = data;
    }
    @Override
    public void run() {
    for (int i = 0; i < 5; i++) {
    shareData.dec();
    } } }
    /**
    *共享数据类
    **/
    class ShareData{
    private int num = 10 ;
    public synchronized void inc() {
    num++;
    System.out.println(Thread.currentThread().getName()+”: invoke inc method num =” + num);
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } } }

  2. 将这些 Runnable 对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable 对象调用外部类的这些方法。

    public static void main(String[] args) {
    //公共数据
    final ShareData shareData = new ShareData();
    for (int i = 0; i < 4; i++) {
    if(i%2 == 0){
    new Thread(new Runnable() {
    @Override
    public void run() {
    for (int i = 0; i < 5; i++) {
    shareData.inc();
    } }
    },“Thread “+ i).start();
    }else{
    new Thread(new Runnable() {
    @Override
    public void run() {
    for (int i = 0; i < 5; i++) {
    shareData.dec();
    } }
    },“Thread “+ i).start();
    } } }
    class ShareData{
    private int num = 10 ;
    public synchronized void inc() {
    num++;
    System.out.println(Thread.currentThread().getName()+”: invoke inc method num =” + num);
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } }
    public synchronized void dec() {
    num–;
    System.err.println(Thread.currentThread().getName()+”: invoke dec method num =” + num);
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } } }

补充:上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的 Runnable 对象作为外部类中的成员内部类或局部内部类。
总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。
(二)多线程基础知识–线程并发库*
Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包。这个包包含有一系列能够让 Java 的并发编程变得更加简单轻松的类。在这个包被添加以前,你需要自己去动手实现自己的相关工具类。下面带你认识下java.util.concurrent 包里的这些类,然后你可以尝试着如何在项目中使用它们。本文中将使用 Java 6 版本,我不确定这和 Java 5 版本里的是否有一些差异。我不会去解释关于 Java 并发的核心问题 – 其背后的原理,也就是说,如果你对那些东西感兴趣,参考《Java 并发指南》。
( 1 ) Java 的线程并发库介绍
Java5 的多线程并有两个大发库在 java.util.concurrent 包及子包中,子包主要的包有一下两个
1) java.util.concurrent 包 (多线程并发库)
➢ java.util.concurrent 包含许多线程安全、测试良好、高性能的并发构建块。不客气地说,创建java.util.concurrent 的目的就是要实现 Collection 框架对数据结构所执行的并发操作。通过提供一组可靠的、高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性,后面、我们会做介绍。
➢如果一些类名看起来相似,可能是因为 java.util.concurrent 中的许多概念源自 Doug Lea 的util.concurrent 库。
2) java.util.concurrent.atomic 包 (多线程的原子性操作提供的工具类)
➢查看 atomic 包文档页下面的介绍,它可以对多线程的基本数据、数组中的基本数据和对象中的基本数据
进行多线程的操作(AtomicInteger、AtomicIntegerArray、AtomicIntegerFieldUpDater…) ➢通过如下两个方法快速理解 atomic 包的意义:
 AtomicInteger 类的 boolean compareAndSet(expectedValue, updateValue);
 AtomicIntegerArray 类的 int addAndGet(int i, int delta);
➢顺带解释 volatile 类型的作用,需要查看 java 语言规范。
 volatile 修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。(具有可见性)
 volatile 没有原子性。
3) java.util.concurrent.lock 包 (多线程的锁机制)
为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。该框架允许更灵活地使用锁和条件。
本包下有三大接口,下面简单介绍下:
➢Lock 接口:支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 handover-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。 ➢ReadWriteLock 接口:以类似方式定义了一些读取者可以共享而写入者独占的锁。此包只提供了一个实现,即 ReentrantReadWriteLock,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的、适用于非标准要求的实现。
➢Condition 接口:描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
( 2 ) Java 的并发库入门
下面我们将分别介绍 java.util.concurrent 包下的常用类的使用。

  1. java.util.concurrent 包
    java.util.concurrent 包描述:
    在并发编程中很常用的实用工具类。此包包括了几个小的、已标准化的可扩展框架,以及一些提供有用功能的类。此包下有一些组件,其中包括:
     执行程序(线程池)
     并发队列
     同步器
     并发 Collocation
    下面我们将 java.util.concurrent 包下的组件逐一简单介绍:
    A. 执行程序
    ➢Executors 线程池工厂类
    首次我们来说下线程池的作用:
    线程池作用就是限制系统中执行线程的数量。
    根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程 排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程 池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
    为什么要用线程池:  减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
     可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(每个线程需要大约 1MB 内存,线程开的越多,消耗的内存也就越大,最后死机)
    Executors 详解:
    Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。ThreadPoolExecutor 是 Executors 类的底层实现。我们先介绍下 Executors。
    线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
    Java5 中并发库中,线程池创建线程大致可以分为下面三种:

    //创建固定大小的线程池
    ExecutorService fPool = Executors.newFixedThreadPool(3);
    //创建缓存大小的线程池
    ExecutorService cPool = Executors.newCachedThreadPool();
    //创建单一的线程池
    ExecutorService sPool = Executors.newSingleThreadExecutor();

下面我们通过简单示例来分别说明:
 固定大小连接池

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
/**
* Java 线程:线程池- ** @author Administrator 2009-11-4 23:30:44
*/
public class Test {
public static void main(String[] args) {
//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//创建实现了 Runnable 接口对象,Thread 对象当然也实现了 Runnable 接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
//将线程放入池中进行执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//关闭线程池
pool.shutdown();
}}
class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在执行。。。");
} }

运行结果:
pool-1-thread-1 正在执行。。。
pool-1-thread-1 正在执行。。。
pool-1-thread-2 正在执行。。。
pool-1-thread-1 正在执行。。。
pool-1-thread-2 正在执行。。。
从上面的运行来看,我们 Thread 类都是在线程池中运行的,线程池在执行 execute 方法来执行 Thread 类中的 run 方法。不管 execute 执行几次,线程池始终都会使用 2 个线程来处理。不会再去创建出其他线程来处理run 方法执行。这就是固定大小线程池。
 单任务连接池
我们将上面的代码

//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
改为:
//创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
ExecutorService pool = Executors.newSingleThreadExecutor();

运行结果:
pool-1-thread-1 正在执行。。。
pool-1-thread-1 正在执行。。。
pool-1-thread-1 正在执行。。。
pool-1-thread-1 正在执行。。。
pool-1-thread-1 正在执行。。。
运行结果看出,单任务线程池在执行 execute 方法来执行 Thread 类中的 run 方法。不管 execute 执行几次,线程池始终都会使用单个线程来处理。
补充:在 java 的多线程中,一但线程关闭,就会成为死线程。关闭后死线程就没有办法在启动了。再次启动就会出现异常信息:Exception in thread “main” java.lang.IllegalThreadStateException。那么如何解决这个问题呢?
我们这里就可以使用 Executors.newSingleThreadExecutor()来再次启动一个线程。(面试)
 可变连接池

//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
改为:
//创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
ExecutorService pool = Executors.newCachedThreadPool();

运行结果:
pool-1-thread-5 正在执行。。。
pool-1-thread-1 正在执行。。。
pool-1-thread-4 正在执行。。。
pool-1-thread-3 正在执行。。。
pool-1-thread-2 正在执行。。。
运行结果看出,可变任务线程池在执行 execute 方法来执行 Thread 类中的 run 方法。这里 execute 执行多次,线程池就会创建出多个线程来处理 Thread 类中 run 方法。所有我们看到连接池会根据执行的情况,在程序运行时创建多个线程来处理,这里就是可变连接池的特点。
那么在上面的三种创建方式,Executors 还可以在执行某个线程时,定时操作。那么下面我们通过代码简单演示
下。
 延迟连接池

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Java 线程:线程池- ** @author Administrator 2009-11-4 23:30:44
*/
public class Test {
public static void main(String[] args) {
//创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
//创建实现了 Runnable 接口对象,Thread 对象当然也实现了 Runnable 接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
//将线程放入池中进行执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
//使用定时执行风格的方法
pool.schedule(t4, 10, TimeUnit.MILLISECONDS); //t4 和 t5 在 10 秒后执行
pool.schedule(t5, 10, TimeUnit.MILLISECONDS);
//关闭线程池
pool.shutdown();
}}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行。。。");
}}

运行结果:
pool-1-thread-1 正在执行。。。
pool-1-thread-2 正在执行。。。
pool-1-thread-1 正在执行。。。
pool-1-thread-1 正在执行。。。
pool-1-thread-2 正在执行。。。
➢ExecutorService 执行器服务
java.util.concurrent.ExecutorService 接口表示一个异步执行机制,使我们能够在后台执行任务。因此一个 ExecutorService 很类似于一个线程池。实际上,存在于 java.util.concurrent 包里的 ExecutorService 实现就是一个线程池实现。
ExecutorService 例子:以下是一个简单的 ExecutorService 例子:

//线程工厂类创建出线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//执行一个线程任务
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
//线程池关闭
executorService.shutdown();

上面代码首先使用 newFixedThreadPool() 工厂方法创建一个 ExecutorService。这里创建了一个十个线程执行任务的线程池。然后,将一个 Runnable 接口的匿名实现类传递给 execute() 方法。这将导致 ExecutorService 中的某个线程执行该 Runnable。这里可以看成一个任务分派,示例代码中的任务分派我们可以理解为:
一个线程将一个任务委派给一个 ExecutorService 去异步执行。
一旦该线程将任务委派给 ExecutorService,该线程将继续它自己的执行,独立于该任务的执行。
如下图:JavaSE(7续b)面试_第1张图片
ExecutorService 实现:
既然 ExecutorService 是个接口,如果你想用它的话就得去使用它的实现类之一。
java.util.concurrent 包提供了 ExecutorService 接口的以下实现类:
 ThreadPoolExecutor
 ScheduledThreadPoolExecutor
ExecutorService 创建:
ExecutorService 的创建依赖于你使用的具体实现。但是你也可以使用 Executors 工厂类来创建
ExecutorService 实例。代码示例:

ExecutorService executorService1 = Executors.newSingleThreadExecutor(); //之前 Executors 已介绍
ExecutorService executorService2 = Executors.newFixedThreadPool(10);
ExecutorService executorService3 = Executors.newScheduledThreadPool(10);

ExecutorService 使用:
有几种不同的方式来将任务委托给 ExecutorService 去执行:
 execute(Runnable)
 submit(Runnable)
 submit(Callable)
 invokeAny(…)  invokeAll(…)
接下来我们挨个看一下这些方法。
✓ execute(Runnable)
execute(Runnable) 方法要求一个 java.lang.Runnable 对象,然后对它进行异步执行。以下是使用ExecutorService 执行一个 Runnable 的示例:

//从 Executors 中获得 ExecutorService 
ExecutorService executorService = Executors.newSingleThreadExecutor();
//执行 ExecutorService 中的方法
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
//线程池关闭

✓ submit(Runnable)
submit(Callable) 方法类似于 submit(Runnable) 方法,除了它所要求的参数类型之外。Callable 实例除了它的 call() 方法能够返回一个结果之外和一个 Runnable 很相像。Runnable.run() 不能够返回一个结果。
Callable 的结果可以通过 submit(Callable) 方法返回的 Future 对象进行获取。
以下是一个 ExecutorService Callable 示例:

//从 Executors 中获得 ExecutorService 
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(new Callable(){
public Object call() throws Exception {
System.out.println("Asynchronous Callable");
return "Callable Result";
}
});
System.out.println("future.get() = " + future.get());
executorService.shutdown();

输出:
Asynchronous Callable
future.get() = Callable Result
✓ invokeAny()
invokeAny() 方法要求一系列的 Callable 或者其子接口的实例对象。调用这个方法并不会返回一个 Future,但它返回其中一个 Callable 对象的结果。无法保证返回的是哪个 Callable 的结果 – 只能表明其中一个已执行结束。
如果其中一个任务执行结束(或者抛了一个异常),其他 Callable 将被取消。以下是示例代码:

ExecutorService executorService = Executors.newSingleThreadExecutor();
Set> callables = new HashSet>();
callables.add(new Callable() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable() {
public String call() throws Exception {
return "Task 3";
}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();

上述代码将会打印出给定 Callable 集合中的一个的执行结果。我自己试着执行了它几次,结果始终在变。有时是 “Task 1″,有时是 “Task 2″ 等等。
✓ invokeAll()
invokeAll() 方法将调用你在集合中传给 ExecutorService 的所有 Callable 对象。invokeAll() 返回一系列
的 Future 对象,通过它们你可以获取每个 Callable 的执行结果。
记住,一个任务可能会由于一个异常而结束,因此它可能没有 “成功”。无法通过一个 Future 对象来告知我们是两种结束中的哪一种。
以下是一个代码示例:

ExecutorService executorService = Executors.newSingleThreadExecutor();
Set> callables = new HashSet>();
callables.add(new Callable() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable() {
public String call() throws Exception {
return "Task 3";
}
});
List> futures = executorService.invokeAll(callables);
for(Future future : futures){
System.out.println("future.get = " + future.get());
}
executorService.shutdown();

输出结果:
future.get = Task 3
future.get = Task 1
future.get = Task 2
Executors 关闭:
使用 shutdown 和 shutdownNow 可以关闭线程池
两者的区别:
shutdown 只是将空闲的线程 interrupt() 了,shutdown()之前提交的任务可以继续执行直到结束。
shutdownNow 是 interrupt 所有线程, 因此大部分线程将立刻被中断。之所以是大部分,而不是全部 ,是因为 interrupt()方法能力有限。

你可能感兴趣的:(java面试)