线程的第三种写法:
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
回忆:(Runnable)
Runnable封装一个异步运行的任务,可以把它想象成为一个没有参数和返回值的异步方法.
Callable与Runnable相似,但是Callable有返回值.Callable接口是一个参数化的类型,只有一个方法call().
类型参数是返回值的类型,
例如:
Callable<Integer>表示一个最终返回Integer对象的异步计算.
Future保存异步计算的结果.可以启动一个计算,将Future对象教给某个线程,然后忘掉它.
Future对象所有者在结果计算好之后就可以获得它.
Future接口具有以下的方法:
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
//获取结果,如果没有结果可用,就阻塞直到真正得到结果超过指定的时间为止.如果不成功,第二个方法就会抛出TimeoutException异常.
boolean cancel(boolean mayInterrupt);
//尝试取消这一任务的运行.如果任务已经开始,并且mayInterrupt参数值为true,它就会被中断.如果成功执行了取消操作,返回true.
boolean isCancelled();
//如果任务在完成前被取消了,则返回true.
boolean isDone();
//如果任务结束,无论是正常结束,中途取消或发生异常,都返回true.
FutureTask包装器是一种非常便利的机制,它可以将Callable转换为Future和Runnable,它同时实现二者的接口.
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask task = new FutureTask(() -> {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.sleep(1000);//线程睡眠一秒
return "结束";
});
//创建和启动新的线程
new Thread(task).start();
//获取返回结果
System.out.println(task.get());
}
执行器<Executor>
构建一个新的线程是有一定的代价的,因为涉及与操作系统的交互,如果程序中创建了大量的生命期很短的线程,应该使用线程池(thread pool).一个线程池中包含许多准备运行的空闲线程.将Runnable对象交给线程池,就会有一个线程调用Run方法.当Run方法退出时,线程不会死亡,而是在池中准备为下一个请求提供服务.
另一个使用线程池的理由:
减少并发线程的数目.
创建大量线程会大大降低性能甚至使虚拟机崩溃.如果有一个会创建多个虚拟机的算法,应该使用一个线程数"固定的"线程池以限制并发的线程总数.
执行器<Executor>类有许多静态工厂方法用来构造线程池.
方法 | 描述 | 应用场景 |
缓冲线程池 Executors.newCachedThreadPool() | 核心线程数是:0;最大线程数是Integer的最大值(救急线程可以无限创建);生存时间是60s. | 适合任务数比较密集,但每个任务执行时间较短的情况. |
固定大小的线程池 Executors.newFixedThreadPool(x) |
x为需要创建的线程数,固定的值,但是可以修改,空闲线程会一直被保留; 核心线程数==最大线程数(没有救急线程被创建) 阻塞队列 无界 可以存放任意数量的任务 | 适合执行数量有限,长时间运行的任务 |
单线程线程池 Executors.newSingleThreadExecutor() | 只有一个线程的线程池,该线程顺序执行每一个提交的任务 | 希望多个任务进行排队的时候执行 |
区别:
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
int getLargPoolSize:返回线程在该执行器生命周期中的最大尺寸.
一个核心的ExecutorService的实现类:ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
corePoolSize 核心线程数目 (最多保留的线程数)
5
maximumPoolSize
10
workQueue 阻塞队列 如果任务超过了核心线程数,进入队列进行排队,直到有空闲的线程
10
如果任务过多,阻塞队列都放不下了,还会创建新的线程来救急
corePoolSize+救急的线程 <= maximumPoolSize(最大线程数)
21 会抛出拒绝提交任务异常
keepAliveTime 生存时间- 针对救急线程
60
unit 时间单位 秒
带有日程安排功能的线程池:
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
// 让任务推迟一段时间执行
// 参数1.任务对象, 参数2,3 推迟的时间
/*service.schedule(()->{
System.out.println("执行任务...");
}, 10L, TimeUnit.SECONDS);*/
// 以一定的频率反复执行任务(任务不会重叠)
// 参数1,任务对象, 参数2,初始推迟时间, 参数3,4 时间间隔和单位
/*service.scheduleAtFixedRate(()->{
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}, 0, 1, TimeUnit.SECONDS);*/
// delay表示从上一个任务结束,到下一个任务开始之间的时间
service.scheduleWithFixedDelay(()->{
System.out.println("hello");
}, 0, 1, TimeUnit.SECONDS);
// service.shutdown();
线程池执行:
newCachedThreadPool方法创建了一个线程池,对于每个任务,如果有空闲线程可用,立即让它执行任务,如果没有可用的空闲线程,则创建一个新的线程.
newFixedThreadPool方法创建了一个具有固定大小的线程池,如果提交 的任务数多于空闲的线程数,那么把得不到服务的任务方法队列中,当其他任务完成以后再运行它们.
newSingleThreadExecutor是一个退化了的大小为一的线程池,有一个线程执行提交的任务,一个接着一个.
这三个方法返回实现了executorSerevice接口的ThreadPoolExecutor类的对象.
总结在使用连接池时应该做的事情:
1.调用了Executors类中的静态方法newCachedThreadPool或者newFixedThreadPool
2,调用submit提交Runnable或者Callable对象.
3.如果想要取消一个任务,或者提交Callable对象,那就要保存好返回的future对象.
4,当不在提交任何任务时,调用shutdown,结束.
线程安全集合类:
StringBuffer 线程安全
String 不可变类 线程安全
Random 线程安全
Vectr 实现了List 并且线程安全
Hashtable 实现了map,并且线程安全
jdk1.5新增的线程安全类
ConcurrentHashMap 实现了Map,并且线程安全
ConcurrentSkipLsitMap 实现了Map(可排序),并且线程安全
CopyOnWriteArrayList实现了List, 并且线程安全
BlockingQueue阻塞队列
方法 | 正常动作 | 特殊情况下的动作 |
add | 添加一个元素 | 如果队列满,则抛出一个IllegalStateException异常 |
element | 返回队列的头元素 |
如果队列空,抛出NoSuchElementException异常 |
offer | 添加一个元素并且返回true | 如果队列满,则返回false |
peek | 返回队列的头元素 | 如果队列空,则返回null |
poll | 移除并返回队列的头元素 | 如果队列空,则返回null |
put | 添加一个元素 | 如果队列满,则阻塞 |
remove | 移除并返回头元素 | 如果队列空,则抛出NoSuchElementException异常 |
take | 移除并返回头元素 | 如果队列空,则阻塞 |
// 线程安全的队列, 其中capacity 表示队列的元素上限
BlockingQueue<String> queue1 = new ArrayBlockingQueue<>(3);
// 或 new LinkedBlockingDeque<>(3);
生产者/消费者模式
private static BlockingQueue<chanpin> queue = new ArrayBlockingQueue<>(5);
public static void main(String[] args) {
//生产者线程
new Thread(() -> {
for (int i = 0; i < 10; i++) {
chanpin c = new chanpin(i);
System.out.println(Thread.currentThread().getName()+"生产了" + c);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
queue.put(c);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//消费者线程
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 3; j++) {
try {
Thread.sleep(1000);
chanpin c = queue.take();
System.out.println(Thread.currentThread().getName()+"消费了" + c);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
class chanpin {
private int i;
public chanpin() {
}
public chanpin(int i) {
this.i = i;
}
@Override
public String toString() {
return "chanpin{" +
"i=" + i +
'}';
}