1、用多线程的目的是什么?
充分利用cpu资源,并发做多件事
2、单核cpu机器上适不适合用多线程?
适合,如果是单线程,线程中需要等待IO时,CPU就会空闲出来
3、线程什么时候会让出CPU?
- 阻塞时,wait、await、等待IO
- Sleep时。注:sleep抢到的锁是不会释放的
- yield。把CPU让出来,大家再抢占CPU资源的方法。
- 结束了
4、线程是什么?
一条代码执行流,完成一组代码的执行。
这一组代码,我们往往称为一个任务。
5、创建线程的方式有几种?
一种,new Thread
6、"任务"有几种写法?
实现Runnable接口
创建Callable实例
集成Thread,修改run方法
7、CPU做的是什么工作?
执行代码,处理指令
8、线程是不是越多越好?
不是。因为
1、线程在java中是一个对象,每一个java线程都需要一个操作系统线程支持。线程创建销毁需要时间。如果 创建时间+销毁时间>执行任务时间 就很不合算。
2、线程过多,会消耗很多的内存。java对象占用堆内存,操作系统线程占用系统内存,根据jvm规范,一个线程默认最大(64位)栈大小为1m(超了报OOM),这个栈控件是需要从系统内存中分配的。
3、操作系统需要频繁切换线程上下文(大家都想被运行),影响性能
9、该如何正确使用多线程?
多线程的目的:充分利用cpu并发做事(多做事)
线程的本质:将代码送给cpu运行
用合适数量的线程不断运送代码即可
这个核实数量的线程就构成了一个池
10、线程池原理?
接收任务,放入仓库
工作线程从仓库取任务,执行
当没有任务时,线程阻塞,当有任务时,唤醒线程执行。
(注:仓库用)
11、仓库用什么?
BlockingQueue阻塞队列,线程安全的
在队列为空时的获取阻塞,在队列满时放入阻塞。
BlockingQueue方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:
第一种是抛出一个异常
第二种是返回一个特殊值(null或false,具体取决于操作)
第三种是在操作可以成功前,无限期地阻塞当前线程
第四种是在放弃前 只在给定的最大时间限制内阻塞
抛出异常 | 特殊值 | 阻塞 | 超时 | |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,unit) |
检查 | element() | peak() | 不可用 | 不可用 |
12、手撸线程池
public class FixedSizeThreadPool{
//仓库
private BlockingQueue<Runnable> taskQueue;
//放线程的集合
private List<Worker> workers;
private volatile boolean working = true;//并发情况下变量的可见性
public FixedSizeThreadPool(int poolSize, int taskQueueSize){
if(poolSize <= 0 || taskQueueSize <= 0){
throw new IllegalArgumentException("参数错误");
}
//初始化任务队列
this.taskQueue = new LinkedBlockingQueue<>(taskQueueSize);
//创建放线程的集合
this.workers = new ArrayList<>();
//初始化工作线程
for(int i=0; i<poolSize;i++){
Worker w = new Worker(this);
this.workers.add(w);
w.start();
}
}
public boolean submit(Runnable task){
return this.taskQueue.offer(task);
}
public void shutDown(){
if(this.working){
this.working = false;
//如果工作线程处于阻塞状态的,唤醒
for(Thread t : this.workers){
if(t.getState().equals(State.BLOCKED) || t.getState().equals(State.WAITING)){
t.interrupt(); //中断阻塞状态
}
}
}
}
//工作线程
private class Worker extends Thread{
private FixedSizeThreadPool pool;
public Worker(FixedSizeThreadPool pool){
this.pool = pool;
}
public void run(){
//方便看效果,加个计数
int taskCount = 0;
//从仓库取任务执行
while(this.pool.working || this.pool.taskQueue.size() > 0){
Runnable task = null;
try{
if(this.pool.working){
task = this.pool.taskQueue.take();
}else{
task = this.pool.taskQueue.poll();
}
}catch(Exception e){
e.printStackTrace();
}
if(task != null){
try{
task.run();
System.out.println(Thread.currentThread().getName() + "执行完成" + (++taskCount) + "个任务");
}catch(Exception e){
e.printStackTrace();
}
}
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args){
FixedSizeThreadPool pool = new FixedSizeThreadPool(3, 5);
//提交任务执行
for(int i=0; i<5; i++){
pool.submit(() -> {
System.out.println("任务开始...");
try{
Thread.sleep(2000L);
}catch(Exception e){
e.printStackTrace();
}
});
}
pool.shutdown();
}
}
13、如何确定合适数量的线程?
如果是计算型任务 --> cpu数量的1-2倍
如果是IO型任务 --> 则需多一些线程,要根据具体的IO阻塞市场进行考量决定。
如tomcat中默认的最大线程数为:200
也可考虑根据需要在一个最小数量和最大数量间自动增减线程数
14、java并发包中提供了哪些线程池的实现
Executor void execute(Runnable command);
ExecutorService ---- 加入了Callable、Future、关闭方法
ForkJoinPool ---- 支持forkJoin框架的线程池实现(归并排序、外排序)
ThreadPoolExecutor ---- 基础、标准的线程池实现
ScheduledExecutorService ---- 对定时任务的支持
Executors ---- 快速得到线程池的工具类
15、慎用Executors,创建线程池的工厂类,它的工厂方法:
-
newFixedThreadPool(int nThreads)
创建一个固定大小、任务队列容量无界的线程池。
池的核心线程数 = 最大线程数 = nThreads
高并发、多任务的场景不适用! -
newCachedThreadPool()
创建一个大小无界的缓冲线程池。他的任务队列是一个同步队列。任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如无 则创建新线程执行。池中的线程空闲超过60秒,将被销毁释放。池中的线程数随任务的多少变化。
池的核心线程数 = 0 最大线程数 = Integer.MAX_VALUE
缓冲线程池适用于耗时较小的异步任务。
16、ThreadPoolExecutor 线程池标准实现
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
网友评论