concurrency
:一核CPU,模拟出来多条线程,快速交替执行。parallellism
:多核CPU ,多个线程可以同时执行;
举例:
并发:是一心多用,听课和看电影,但是CPU大脑只有一个,所以轮着来
并行:火影忍者中的影分身,有多个你出现,可以分别做不同的事情
run()
方法,创建实例,执行start()
方法。public class ThreadDemo1 extends Thread {
@Override
public void run() {
System.out.println("继承Thread实现多线程,名称:"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ThreadDemo1 threadDemo1 = new ThreadDemo1();
threadDemo1.setName("demo1");
// 执行start
threadDemo1.start();
System.out.println("主线程名称:"+Thread.currentThread().getName());
}
run()
方法,创建 Thread 类,使用 Runnable 接口的实现对象作为参数传递给Thread 对象,调用strat()
方法。public class ThreadDemo2 implements Runnable {
@Override
public void run() {
System.out.println("通过Runnable实现多线程,名称:"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ThreadDemo2 threadDemo2 = new ThreadDemo2();
Thread thread = new Thread(threadDemo2);
thread.setName("demo2");
// start线程执行
thread.start();
System.out.println("主线程名称:"+Thread.currentThread().getName());
}
// JDK8之后采用lambda表达式
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("通过Runnable实现多线程,名称:"+Thread.currentThread().getName());
});
thread.setName("demo2");
// start线程执行
thread.start();
System.out.println("主线程名称:"+Thread.currentThread().getName());
}
call()
方法,结合 FutureTask 类包装 Callable 对象,实现多线程。call()
方法,结合多个类比如 FutureTask 和 Thread 类public class MyTask implements Callable<Object> {
@Override
public Object call() throws Exception {
System.out.println("通过Callable实现多线程,名称:"+Thread.currentThread().getName());
return "这是返回值";
}
}
public static void main(String[] args) {
// JDK1.8 lambda表达式
FutureTask<Object> futureTask = new FutureTask<>(() -> {
System.out.println("通过Callable实现多线程,名称:" +
Thread.currentThread().getName());
return "这是返回值";
});
// MyTask myTask = new MyTask();
// FutureTask
// FutureTask继承了Runnable,可以放在Thread中启动执行
Thread thread = new Thread(futureTask);
thread.setName("demo3");
// start线程执行
thread.start();
System.out.println("主线程名称:"+Thread.currentThread().getName());
try {
// 获取返回值
System.out.println(futureTask.get());
} catch (InterruptedException e) {
// 阻塞等待中被中断,则抛出
e.printStackTrace();
} catch (ExecutionException e) {
// 执行过程发送异常被抛出
e.printStackTrace();
}
}
run()
方法,创建线程池,调用执行方法并传入对象。public class ThreadDemo4 implements Runnable {
@Override
public void run() {
System.out.println("通过线程池+runnable实现多线程,名称:" +
Thread.currentThread().getName());
}
}
public static void main(String[] args) {
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
for(int i=0;i<10;i++){
// 线程池执行线程任务
executorService.execute(new ThreadDemo4());
}
System.out.println("主线程名称:"+Thread.currentThread().getName());
// 关闭线程池
executorService.shutdown();
}
run()
方法,实现Runable接口需要实现run()
方法,而Callable是需要实现call()
方法。start()
方法,需要 new 一个 Thread 并发该实现类放入 Thread,再通过新建的 Thread 实例来调用start()
方法。start()
方法。获取返回值只需要借助 FutureTask 实例调用get()
方法即可!线程通常有五种状态,新建、就绪、运行、阻塞和死亡状态:
Thread t = new Thread();
start()
方法后,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()
此线程立即就会执行。wait()
方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()
或notifyAll()
方法才能被唤 醒,wait()
是 Object 类的方法。sleep()
方法,或者调用了其他线程的join()
方法,或者发出了 I/O 请求时,就会进入这个状态。线程会进入到阻塞状态。当sleep()
状态超时、join()
等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入就绪状态。run()
方法,该线程结束生命周期。sleep()
:属于线程 Thread 的方法,让线程暂缓执行,等待预计时间之后再恢复,交出CPU使用权,不会释放锁,抱着锁睡觉!进入超时等待状态TIME_WAITGING,睡眠结束变为就绪Runnableyield()
:属于线程 Thread 的方法,暂停当前线程的对象,去执行其他线程,交出CPU使用权,不会释放锁,和sleep()
类似,让相同优先级的线程轮流执行,但是不保证一定轮流,
join()
:属于线程 Thread 的方法,在主线程上运行调用该方法,会让主线程休眠,不会释放锁,让调用join()
方法的线程先执行完毕,再执行其他线程。类似让救护车警车优先通过!!wait()
:属于 Object 的方法,当前线程调用对象的 wait()
方法,会释放锁,进入线程的等待队列,需要依靠notify()
或者notifyAll()
唤醒,或者wait(timeout)
时间自动唤醒。notify()
:属于 Object 的方法,唤醒在对象监视器上等待的单个线程,随机唤醒。notifyAll()
:属于Object 的方法,唤醒在对象监视器上等待的全部线程,全部唤醒run()
:普通的方法调用run()
函数,在主线程中执行,不会新建一个线程来执行。
start()
:新启动一个线程,这时此线程处于就绪(可运行)状态,并没有真正运行,一旦得到 CPU 时间片,就调用 run()
方法执行线程任务。
使用线程池的好处:
重用存在的线程,减少对象创建销毁的开销,有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,且可以定时定期执行、单线程、并发数控制,配置任务过多任务后的拒绝策略等功能。
类别:
newFixedThreadPool
:一个定长线程池,可控制线程最大并发数。newCachedThreadPool
:一个可缓存线程池。newSingleThreadExecutor
:一个单线程化的线程池,用唯一的工作线程来执行任务。newScheduledThreadPool
:一个定长线程池,支持定时/周期性任务执行。【阿里巴巴编码规范】 线程池不允许使用 Executors 去创建,要通过 ThreadPoolExecutor的方式原因?
Executors创建的线程池底层也是调用 ThreadPoolExecutor,只不过使用不同的参数、队列、拒绝策略等如果使用不当,会造成资源耗尽问题。直接使用ThreadPoolExecutor让使用者更加清楚线程池允许规则,常见参数的使用,避免风险。
常见的线程池问题:
1.newFixedThreadPool和newSingleThreadExecutor:
队列使用LinkedBlockingQueue,队列长度为 Integer.MAX_VALUE,可能造成堆积,导致OOM
2.newScheduledThreadPool和newCachedThreadPool:
线程池里面允许最大的线程数是Integer.MAX_VALUE,可能会创建过多线程,导致OOM
ThreadPoolExecutor构造函数里面的参数,能否解释下各个参数的作用?
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
:核心线程数,线程池也会维护线程的最少数量,默认情况下核心线程会一直存活,即使没有任务也不会受存 keepAliveTime 控制!
坑:在刚创建线程池时线程不会立即启动,到有任务提交时才开始创建线程并逐步线程数目达到 corePoolSize。
maximumPoolSize
:线程池维护线程的最大数量,超过将被阻塞!
坑:当核心线程满,且阻塞队列也满时,才会判断当前线程数是否小于最大线程数,才决定是否创建新线程
keepAliveTime
:非核心线程的闲置超时时间,超过这个时间就会被回收,直到线程数量等于 corePoolSize。
unit
:指定 keepAliveTime 的单位,如 TimeUnit.SECONDS、TimeUnit.MILLISECONDS
workQueue
:线程池中的任务队列,常用的是 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。
threadFactory
:创建新线程时使用的工厂
handler
:RejectedExecutionHandler 是一个接口且只有一个方法,线程池中的数量大于 maximumPoolSize,对拒绝任务的处理策略,默认有 4 种策略:
AbortPolicy
CallerRunsPolicy
DiscardOldestPolicy
DiscardPolicy
AbortPolicy
:中止策略。默认的拒绝策略,直接抛出 RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。
DiscardPolicy
:抛弃策略。什么都不做,直接抛弃被拒绝的任务。
DiscardOldestPolicy
:抛弃最老策略。抛弃阻塞队列中最老的任务,相当于就是队列中下一个将要被执行的任务,然后重新提交被拒绝的任务。如果阻塞队列是一个优先队列,那么“抛弃最旧的”策略将导致抛弃优先级最高的任务,因此最好不要将该策略和优先级队列放在一起使用。
CallerRunsPolicy
:调用者运行策略。在调用者线程中执行该任务。该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者(调用线程池执行任务的主线程),由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得线程池有时间来处理完正在执行的任务。
图片参考:https://joonwhee.blog.csdn.net/article/details/115364158
总结的面试题也挺费时间的,文章会不定时更新,有时候一天多更新几篇,如果帮助您复习巩固了知识点,还请三连支持一下,后续会亿点点的更新!
为了帮助更多小白从零进阶 Java 工程师,从CSDN官方那边搞来了一套 《Java 工程师学习成长知识图谱》,尺寸 870mm x 560mm
,展开后有一张办公桌大小,也可以折叠成一本书的尺寸,有兴趣的小伙伴可以了解一下,当然,不管怎样博主的文章一直都是免费的~