1.什么是线程
线程是操作系统运行调度的最小单元,是进程的元素,进程是由多个线程组合而来的。
每条线程有自己的栈内存,一个进程内的所有线程共享一片堆内存
多线程可以对运算密集型任务提速
2.简单描述一下线程安全
在多线程环境下运行的代码,如果存在多个线程资源共享就存在线程安全的问题,例如同时操作一个成员变量
3.Thread 类中的start() 和 run() 方法有什么区别
start是启动线程的方法,run方法内部会调用目标对象的run方法,目标对象的run方法是线程运行的载体
直接调用run方法也会运行,只是在当前线程中同步运行,并没有新的线程启动
4.Java多线程中调用wait() 和 sleep()方法有什么不同
sleep方法是Thread类提供的,wait方法是Object提供的
sleep只能等到休眠时间到,自己醒来,wait可以自己醒来,也可以被唤醒
sleep不会释放锁,wait会释放锁
wait必须在synchronized环境内使用,因为会释放锁,没有synchronized何来锁
5.notify和notifyAll的区别
当一个线程wait后,只有通过notify或notifyAll才能被唤醒,也就是重新获得了锁竞争的资格
notify只能唤醒某个线程,notifyAll可以唤醒所有的线程(所以如果多个线程被阻塞,须使用该方法)
注意获得竞争锁的资格不是获得了锁
6.为什么wait, notify 和 notifyAll这些方法不在thread类里面
这三个方法都是锁级别的操作,比如调用wait的时候需要去释放对象的锁,如果定义在thread里面,当前线程并不知道要释放的是哪个对象的锁,因此定义在Object里面,由被锁的对象去调用更为合理。
同理notify和notifyAll这两个方式是去唤醒被锁对象的阻塞线程,如果定义在thread里面,那么当前线程还是不得知需要唤醒的是哪个被锁对象的阻塞线程,因此也定义在Object里面
7.为什么wait和notify方法要在同步块中调用
如果在同步块外调用会抛异常:java.lang.IllegalMonitorStateException: current thread not owner
8.Thread类中的yield方法有什么作用
可以暂停当前线程,让其他线程获取更高的CPU执行优先级,但不保证当前线程一定后执行
9.Java线程池中submit() 和 execute()方法有什么区别
都可以提交线程,只是返回不同,submit定义在ExecutorService中,可以返回一个线程计算结果对象Future
10.如何强制启动一个线程
java中没有方法可以强制启动线程
11.interrupte、interrupted 和 isInterrupted的区别
interrupte是Thread的成员方法,中断线程
interrupted是Thread的静态方法,如果当前线程已被中断,则取消中断状态
isInterrupted是Thread的成员方法,判断当前线程是否中断
12.怎么检测一个线程是否拥有锁
Thread类提供了holdsLock(Object o)方法可以检测到o对象是否持有当前线程的锁,true表示有锁
13.如何让线程按既定顺序执行,线程的优先级
t1,t2,t3,先启动t3,t3中启动t2,t2中启动t1,执行顺序t1>t2>t3
setPriority(int newPriority) 调用当前线程的该方法可以设置线程执行顺序优先级,但不保证一定先执行,只是概率高了一些
14.什么是竞态条件.
多个线程同时竞争一个共享资源,并且对该资源进行更新操作(例如线程A 对2 +1,线程B对变量2+1,那么想要的结果应该是4,但是其实是3),那么各个线程会发生覆盖的现象,称为竞态条件
避免竞态条件的发生,可以对资源加同步锁
15.什么是ThreadLocal变量
ThreadLocal变量是本地线程的变量,是一种对共享变量的副本的拷贝,可以实现各个线程操作共享变量的副本,即共享变量变成了当前线程的私有属性,各个线程间的更新操作互不影响
与synchronized的区别:同步是指多个线程再操作共享资源的时候,同步进行,但操作的还是共享资源,而ThreadLocal则是对共享资源的一份拷贝,多个线程各自操作各自的拷贝,互不干扰
使用示例:
*ThreadLocal变量使用示例
//实体类
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person [name=" + name + "]";
}
}
//使用ThreadLocal
public class Task1 implements Runnable{
private ThreadLocal threadLocal = new ThreadLocal() {
protected Person initialValue() {
return new Person();
}
};
@Override
public void run() {
System.out.println("ThreadName:" + Thread.currentThread().getName() + " " + threadLocal.get());
threadLocal.get().setName("t");
System.out.println("ThreadName:" + Thread.currentThread().getName() + " " + threadLocal.get());
}
}
//未使用ThreadLocal
public class Task2 implements Runnable{
private Person person = new Person();
@Override
public void run() {
System.out.println("ThreadName:" + Thread.currentThread().getName() + " " + person);
person.setName("t");
System.out.println("ThreadName:" + Thread.currentThread().getName() + " " + person);
}
}
//test
public class Test {
public static void main(String[] args) {
//使用ThreadLocal
test1();
//未使用ThreadLocal
//test2();
}
public static void test1() {
Task1 task1 = new Task1();
for(int i = 0; i < 5; i++) {
new Thread(task1).start();
}
}
public static void test2() {
Task2 task2 = new Task2();
for(int i = 0; i < 5; i++) {
new Thread(task2).start();
}
}
}
打印结果:
使用threadlocal,test1():
ThreadName:Thread-1 Person [name=null]
ThreadName:Thread-0 Person [name=null]
ThreadName:Thread-0 Person [name=t]
ThreadName:Thread-3 Person [name=null]
ThreadName:Thread-3 Person [name=t]
ThreadName:Thread-4 Person [name=null]
ThreadName:Thread-4 Person [name=t]
ThreadName:Thread-2 Person [name=null]
ThreadName:Thread-1 Person [name=t]
ThreadName:Thread-2 Person [name=t]
未使用threadlcoal,test2():
ThreadName:Thread-0 Person [name=null]
ThreadName:Thread-0 Person [name=t]
ThreadName:Thread-1 Person [name=t]
ThreadName:Thread-1 Person [name=t]
ThreadName:Thread-2 Person [name=t]
ThreadName:Thread-2 Person [name=t]
ThreadName:Thread-3 Person [name=t]
ThreadName:Thread-3 Person [name=t]
ThreadName:Thread-4 Person [name=t]
ThreadName:Thread-4 Person [name=t]
可以看出使用threadlocal,各个线程独立操作person,未使用threadlocal则相互干扰
*加同步锁的示例
package cn.qu.td;
public class MyThread{
static class Person {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
private static Person person = new Person();
public static void main(String[] args) {
Thread t1 = new Thread("t1"){
public void run() {
synchronized (person) {
person.setId(10L);
person.setName("Lucy");
System.out.println("t1.id:" + person.getId() + " t1.name:" + person.getName());
}
}
};
Thread t2 = new Thread("t2"){
public void run() {
synchronized (person) {
person.setId(20L);
person.setName("Mary");
System.out.println("t2.id:" + person.getId() + " t2.name:" + person.getName());
}
}
};
t1.start();
t2.start();
}
}
上述代码:控制台打印:
t1.id:10 t1.name:Lucy
t2.id:20 t2.name:Mary
因为加锁的缘故,两个线程并未相互干扰
*ThreadLocal一个典型的应用场景就是Hibernate中的session
16.描述一下volatile变量
保证内存可见性,但不保证原子性。
大致意思就是:一个写操作可以被并发读取到,但是没有锁的那种机制保证多个写的原子性。
应用场景:比较底层,通常用在解决变量重排序问题上
17.如何停止一个线程,join的用途
java中不能强制停止一个线程,只能等待其执行完毕后,自行停止
join的字面意思加入的意思,然而我们都知道他在java中的作用是阻塞主线程等待子线程执行结束,那么怎么理解加入这个意义呢
当子线程执行完毕后汇入到主线程,这下可以理解了join的意思啦,详情参考:join详解
例如:
package cn.qu.td;
public class MyThread{
private volatile static Integer a = 10;
public static void main(String[] args) {
Thread t1 = new Thread("t1"){
public void run() {
for(int i = 1; i <= 10; i ++){
a++;
System.out.println(Thread.currentThread().getName() + ": a = " + a);
}
}
};
Thread t2 = new Thread("t2"){
public void run() {
for(int i = 1; i <= 10; i ++){
a++;
System.out.println(Thread.currentThread().getName() + ": a = " + a);
}
}
};
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a);
}
}
可以看到控制台最后一行打印出的a是30,就是等待t1,t2两个线程结束后才执行的最后一行打印a的代码
18.同步块内的线程抛出异常会发生什么
会释放锁,交由其他线程来使用资源
19.多线程编程都注意些什么
*给线程起个有意义且规范的名称
*加同步的时候尽量细粒度,例如能用同步代码块,就不要用同步方法
*合理的使用线程池来管理线程,而不是必须用线程池
*能用jdk提供的线程安全的API就不要用非线程安全的类,然后自己加同步,例如HashMap和ConcurrentHashMap
20.实现线程的几种方式以及区别
继承Thread类方式:
public class MyThread extends Thread {
private Integer ticket = 10;
@Override
public void run() {
while (true){
synchronized (this) {
if(ticket < 1) break;
ticket --;
System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + ticket);
}
}
}
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
}
实现Runnable接口方式:
public class MyThread implements Runnable {
private Integer ticket = 10;
public void run() {
while (true){
synchronized (this) {
if(ticket < 1) break;
ticket --;
System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + ticket);
}
}
}
public static void main(String[] args) {
MyThread t = new MyThread2();
new Thread(t,"t1").start();
new Thread(t,"t2").start();
new Thread(t,"t3").start();
}
}
实现Callable
public class MyThread implements Callable {
protected Integer ticket = 10;
public MyThread call() throws Exception {
while (true) {
synchronized (this) {
if(ticket < 1) break;
ticket --;
System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + ticket);
//模拟线程1进来操作则抛出异常信息
if("t1".equals(Thread.currentThread().getName()))throw new RuntimeException(Thread.currentThread().getName() + ".errortest");
}
}
//返回当前类的引用
return this;
}
public static void main(String[] args) {
MyThread t = new MyThread();
//创建一个线程池,并在线程池内部循环设置线程名称
ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
//定义一个随方法调用的自增变量
private AtomicInteger i = new AtomicInteger(1);
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
//设置线程名称
thread.setName("t" + i.getAndIncrement());
return thread;
}
});
//声明一个线程执行完成后返回结果的存储集合
List> list = new ArrayList>();
for(int i = 0; i < 3; i ++){
//调用submit(Callable c)启动线程
Future future = executorService.submit(t);
//把线程运行的返回结果保存到集合中
list.add(future);
}
//线程运行完毕后,关闭阻塞
executorService.shutdown();
String error = "";
//迭代线程运行的返回结果
for (Future future : list) {
try{
System.out.println("剩余票数:" + future.get().ticket);
}catch (Exception e) {
//如果某个线程抛出了异常则赋值给error
error = e.getMessage();
}
}
//打印出错线程的异常信息
System.out.println(error);
}
}
*Thread和Runnable的区别:
通过上述代码我们可以看到,使用Thread方式不方便线程间资源共享,当然了你可以把一个实现线程的Thread子类在包装给Thread创建的线程对象,这样便可以资源共享了,但这种方式很显然是为了支持Runnable创建线程而提供的构造,毕竟继承了Thread,就可以直接调用Thread的非私有属性和方法了,要是使用Thread包装一个子类,就显得多余,没意义了
Thread和Runnable的区别总结:
1,java不支持多继承,但支持多实现,因此使用Runnable方式比Thread方式更容易扩展
2,使用Runnable可以达到线程间资源共享的目的
*Runnable和Callable的区别:
两者都是接口,便于扩展,Callable的装饰类FutureTask也实现了Runnable,因此都支持线程间资源共享
Runnable是jdk1.0就有,Callable是jdk1.5才有的
Callable有返回值,返回值的类型既Callable的泛型类型,可以自定义返回值类型很方便,Runnable没有返回值
Callable可以把异常抛出去,并被主线程 在获取抛出异常线程的返回值时捕获到,Runnable抛出的异常,主线程无法捕获到
21.谈谈死锁
*死锁概述
多个线程间并发操作同一资源时,产生的相互等待对方的现象
*死锁产生的四个必要条件
互斥条件:一个资源只能同时被一个线程访问。即有同步
请求与保持条件:线程(因需要其他资源)出现等待,且未释放锁. 例如: sleep
不可抢占资源条件:当前线程需要等待的资源,被其他线程持有而且锁定可以理解为不可抢占,即需要等待的资源被加锁
循环等待条件:A线程锁定a资源需要b资源,B线程锁定b资源需要a资源,AB两个线程同时启动,就产生循环等待条件
*一个简单死锁示例
package cn.qu.td;
public class MyThread {
final static Object o1 = new Object(), o2 = new Object();
/*
* o1和o2中都加了同步,因此就 满足互斥条件:一个资源只能同时被一个线程占用
* Thread.sleep(500); 满足请求与保持条件(阻塞且未释放锁,阻塞的目的通常是为了等待其他线程释放资源)
* t2占用o2资源后,t1中也需要o2资源,但是因为t2对o2加了锁,因此就 满足不可抢占资源条件
* t1对o1加锁>阻塞>t2对o2加锁>阻塞>t1获取不到o2等待>t2又获取不到t1也等待 于是满足循环等待条件
*/
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
synchronized (o1) {
System.out.println(o1);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println(o2);
}
}
}
}, "t1").start();
new Thread(new Runnable() {
public void run() {
synchronized (o2) {
System.out.println(o2);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println(o1);
}
}
}
}, "t2").start();
}
}
23.写一个简单的生产者、消费者示例
//商品类
public class Good{
private String name;
private Integer goodNumber;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getGoodNumber() {
return goodNumber;
}
public void setGoodNumber(Integer goodNumber) {
this.goodNumber = goodNumber;
}
@Override
public String toString() {
return "Goods [name=" + name + ", goodNumber=" + goodNumber + "]";
}
//主函数中测试生产者消费者模型
public static void main(String[] args) {
List goods = new ArrayList();
new Thread(new Producer(goods),"生产者").start();
new Thread(new Consumer(goods),"消费者").start();
}
}
//生产者
public class Producer implements Runnable {
//商品容器
private List goods;
public Producer(List goods) {
this.goods = goods;
}
//已生产的商品数量
private Integer i = 0;
public void run() {
while(true){
//休眠的目的是为了模拟动态演示生产和消费的效果
try {
Thread.sleep(500);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (goods) {
//每次进来先唤醒其他的线程
goods.notifyAll();
//打印容器中商品的数量
System.out.println(Thread.currentThread().getName() + "-" + "商品剩余数量:" + goods.size());
if (goods.isEmpty()) { //如果为空,则开始生产商品
Good good = new Good();
good.setName(UUID.randomUUID().toString());
//把生产的商品添加到容器
goods.add(good);
//已生产商品数量加1
i++;
//打印当前生产的商品名称
System.out.println(Thread.currentThread().getName() + "-" + "生产者生又产了一个商品:" + good);
//打印生产者已经生产了多少个商品的数量
System.out.println(Thread.currentThread().getName() + "-" + "生产者已生产的商品数量:" + i);
} else { //如果不为空,则阻塞当前线程
try {
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
//消费者
public class Consumer implements Runnable {
//商品容器
private List goods;
public Consumer(List goods) {
this.goods = goods;
}
//已消费的商品数量
private Integer i = 0;
public void run() {
while (true) {
//休眠的目的是为了模拟动态演示生产和消费的效果
try {
Thread.sleep(500);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (goods) {
//每次进来先唤醒其他的线程
goods.notifyAll();
//打印容器中商品的数量
System.out.println(Thread.currentThread().getName() + "-" + "商品剩余数量:" + goods.size());
if (goods.isEmpty()) { //如果为空,则阻塞当前线程
try {
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else { //否则开始消费商品
Good good = goods.get(0);
goods.remove(good);
//已消费商品的数量加1
i++;
//打印当前消费的商品名称
System.out.println(Thread.currentThread().getName() + "-" + "消费者又消费了一个商品:" + good);
//打印已消费的商品数量
System.out.println(Thread.currentThread().getName() + "-" + "已消费商品的数量:" + i);
}
}
}
}
}
24.什么是线程池
*系统频繁的创建线程和销毁线程对CPU的开销是比较大的,为了让系统减少创建和销毁线程的操作,可以构建一个容器来存放已创建好的线程,供新的任务来使用,可以把这个容器理解为线程池
*使用线程池的目的是为了减少频繁创建销毁线程所带来的开销
*JDK1.5提供了ExecutorService接口来作为线程池的规范继承自顶级接口Executor,以及它的最佳实现ThreadPoolExecutor类
*ThreadPoolExecutor的构造器接收的参数:
int corePoolSize 线程池中空闲线程数量
int maximumPoolSize 线程池中最大线程数量
long keepAliveTime 线程池中空闲线程的保留时间
TimeUnit unit 线程池中空闲线程保留时间的单位:纳秒、微秒、毫秒、秒、分钟、小时、天
BlockingQueue
ThreadFactory threadFactory 创建线程池中所使用线程的工厂
RejectedExecutionHandler handler 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理器(当然需要自己编写)
*ThreadPoolExecutor还提供了两个方法用于扩展每个task执行前后的逻辑
void beforeExecute(Thread t, Runnable r); //线程t start之前执行
void afterExecute(Runnable r, Throwable t); //任务r执行完成后执行,并且可以捕获到r执行异常
*ThreadPoolExecutor 的原理:
维护一个线程集合HashSet
维护一个任务队列LinkedBlockingQueue workQueue 用于execute存放任务
workers中的每个worker就是一个线程,当worker被启动后,会一直循环从workQueue中拿去task,一旦拿到,就调用task的run方法执行,参考runWorker(Worker w)方法就是这个逻辑
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
*JDK1.5还提供了一个构建线程池的工具类Executors,通常我们使用这个工具类来构建一个线程池
*4种常用的线程池(都是ThreadPoolExecutor的对象,只是构建的过程不一样)
newSingleThreadExecutor(应用在多个有序执行场景)
通过Executors.newSingleThreadExecutor()方法可以构建一个单线程的线程池,即线程池始终只有一个线程,所有任务都会由该线程按照队列的方式执行,即先execute(Runnable r)则先执行
newFixedThreadPool(应用在持续低并发场景中)
通过Executors.newSingleThreadExecutor(int maximumPoolSize)或者Executors.newSingleThreadExecutor(int maximumPoolSize,ThreadFactory threadFactory)构建一个固定保留线程数量的线程池,当线程池中的数量达到maximumPoolSize后,则不再创建新的线程,而是使用空闲线程,如果无空闲线程则阻塞直到有空闲线程
newCachedThreadPool(缓存线程池使用最广泛)
*通过Executors.newCachedThreadPool()可以构建一个缓存线程池
*缓存线程池的原理:
当有新的任务时,线程池会查看有无空闲60秒内的线程,如果有则用该线程执行任务,如果没有,则创建新的线程来执行任务,执行完任务空闲后会把该线程缓存到线程池中,缓存的有效时间是60秒,当又有新的任务进来时,会去查看一下,刚才缓存的那个线程是否超过有效时间60秒,如果未超时,则用他来执行任务,否则把他销毁掉,创建一个新的线程来执行任务......
*使用示例以及测试原理:
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
Runnable r1 = new Runnable() {
public void run() {
System.out.println("执行r1任务的线程名称是:" + Thread.currentThread().getName());
}
};
Runnable r2 = new Runnable() {
public void run() {
System.out.println("执行r2任务的线程名称是:" + Thread.currentThread().getName());
}
};
Runnable r3 = new Runnable() {
public void run() {
System.out.println("执行r3任务的线程名称是:" + Thread.currentThread().getName());
}
};
Runnable r4 = new Runnable() {
public void run() {
System.out.println("执行r4任务的线程名称是:" + Thread.currentThread().getName());
}
};
Runnable r5 = new Runnable() {
public void run() {
System.out.println("执行r5任务的线程名称是:" + Thread.currentThread().getName());
}
};
Runnable r6 = new Runnable() {
public void run() {
System.out.println("执行r6任务的线程名称是:" + Thread.currentThread().getName());
}
};
Runnable r7 = new Runnable() {
public void run() {
System.out.println("执行r7任务的线程名称是:" + Thread.currentThread().getName());
}
};
Runnable r8 = new Runnable() {
public void run() {
System.out.println("执行r8任务的线程名称是:" + Thread.currentThread().getName());
}
};
Runnable r9 = new Runnable() {
public void run() {
System.out.println("执行r9任务的线程名称是:" + Thread.currentThread().getName());
}
};
Runnable r10 = new Runnable() {
public void run() {
System.out.println("执行r10任务的线程名称是:" + Thread.currentThread().getName());
}
};
executor.execute(r1);
executor.execute(r2);
executor.execute(r3);
//上述三行因池中没有线程会创建三个线程1,2,3 阻塞100毫秒,好让线程1,2,3执行完任务并空闲
executor.awaitTermination(100, TimeUnit.MILLISECONDS);
executor.execute(r4);
executor.execute(r5);
executor.execute(r6);
//r4,r5,r6执行的时候,刚好有三个空闲线程,则使用空闲线程1,2,3去执行他们
//然后阻塞70秒,目的是为了演示让线程1,2,3在空闲超时60秒,被销毁了
executor.awaitTermination(70, TimeUnit.SECONDS);
//所以,r7,r8,r9,r10进来后,线程池里面没有线程了,又创建了4个线程4,5,6,7去执行他们
executor.execute(r7);
executor.execute(r8);
executor.execute(r9);
executor.execute(r10);
//关闭线程池
executor.shutdown();
}
控制台打印如下:
newScheduledThreadPool (应用在定时调度场景中)
//只执行一次的task
public ScheduledFuture> schedule(Runnable command, long delay, TimeUnit unit);
//只执行一次的task,返回的ScheduledFuture实现Future接口.get方法获取callable的返回值
public
//以下两个方法都是启动一个定时执行的任务,initialDelay多久后第一次执行
//区别是:
scheduleAtFixedRate的period表示上一次执行结束时到下一次开始的时间间隔
scheduleWithFixedDelay的delay表示上一次开始执行时到下一次开始的时间间隔
public ScheduledFuture> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
public ScheduledFuture> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
使用示例:
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Thread t = new Thread(){
@Override
public void run() {
System.out.println(new SimpleDateFormat("dd ss").format(new Date()));
}
};
/*
* para1 表示一个Runnable或Callable的实现
* para2 表示线程第一次执行时间间隔
* para3 表示第一次往后每次执行时间间隔
* para4 表示时间的单位类型
*/
executor.scheduleAtFixedRate(t, 3000, 2000, TimeUnit.MILLISECONDS);
}
控制台会打印:
07 42
07 44
07 46
...
*线程池中阻塞主线程等待池中线程执行完毕的方法 awaitTermination(long timeout, TimeUnit unit)