博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
多线程
阅读量:3960 次
发布时间:2019-05-24

本文共 19361 字,大约阅读时间需要 64 分钟。

目录

多线程

(1)基本概念

程序:一段静态的代码,是一个静态对象;

进程:程序的一次执行过程或一个正在运行的程序,是一个动态的过程;一个进程包括由操作系统分配的内存空间,包含一个或多个线程。

线程:线程是程序内部的一条执行路径,一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

一个Java应用程序java.exe至少有3个线程:main()主线程、gc()垃圾回收线程、异常处理线程;

并行:多个CPU同时执行多个任务;

并发:一个CPU(采用时间片)同时执行多个任务;

什么时候需要多线程:

  • 程序需要同时执行两个或者多个任务;
  • 程序需要是实现一些需要等待的任务时,比如用户输入、文件读写、网络操作、搜索;
  • 需要执行一些后台运行的程序;

(2)线程的生命周期

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

img
  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

(3)线程的创建和使用

Java通过java.lang.Thread类来实现多线程;

Thread类的特性

1.每个线程都是通过某个Thread对象的run()方法来完成操作的,run()方法的主称为线程体

2.通过Thred对象的start()方法来启动这个线程,而不是调用run()方法;

`11

创建方式一

继承Thread类;

步骤

1.创建一个继承Thread类的子类;

2.重写Threa类的run()方法;

3.创建子类的对象;

4.由子类的实例对象调用start()方法来启动这个线程;

Thread类中的常用方法

序号 方法描述
1 public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2 public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3 public final void setName(String name) 改变线程名称,使之与参数 name 相同。
4 public final void setPriority(int priority) 更改线程的优先级。
5 public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
6 public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
7 public void interrupt() 中断线程。
8 public final boolean isAlive() 测试线程是否处于活动状态。

Thread类中的静态方法

序号 方法描述
1 public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
2 public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
3 public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
4 public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
5 public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。

创建方式二

实现Runnable接口;

步骤

1.创建一个类,实现Runnable接口;

2.实现类实现Runnable接口的run()方法;

3.创建实现类的对象;

4.将这个对象作为参数传递到Thread类的构造器中,创建Thread类的对象;

5.由Thread类的对象执行start()方法,启动线程;

开发中优先选择创建方式二:没有类的单继承局限性、更适合处理多个线程共享数据的情况;

创建方式三

实现Callable接口;

步骤

1.创建一个类,实现Callable接口;

2.实现call()方法,将线程要完成的任务声明在call()方法中;

3.创建实现类的对象;

4.将这个对象作为参数传递到FutureTask构造器中,创建FutureTask类的对象;

5.将FutureTask类的对象作为参数传递到Thread类的构造器中,创建Thread类的对象;

6.Thread类对象调用start()方法,启动线程;

实现Callable接口创建线程:可以有返回值;

public class ThreadDemo implements Callable {
@Override public Object call() throws Exception {
//默认返回一个Object类的对象 }}

支持泛型的返回值;当接口声明泛型后,call()方法将返回泛型类型的返回值

public class ThreadDemo implements Callable
{
@Override public Integer call() throws Exception {
}}

借助FutureTask类来获取返回值;

方法可以抛出异常;

Thread.sleep(300);//不需要抛出异常或者使用try-catch

(4)线程的优先级

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

调度策略:时间片、抢占式(高优先级的线程抢占CPU)

调度方式:

1.同优先级的线程组成队列,遵循先进先出的原则,使用时间片策略;

2.低优先级获得调度的概率低,高优先级的线程获得调度的概率高;(只是概率问题,低优先级的线程并不会一定在高优先级线程之后调用)

Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。

方法:getPriority()、setPriority(int newPripority);

说明:线程创建时继承父线程的优先级;

(5)线程同步

一个线程在执行过程中,尚未完成操作,其他线程参与进来操作了共享的数据,使数据的更新不同步,出现安全问题;

解决思路:在线程操作共享数据时,其他线程不能参与进来,直到该线程完成操作;(线程阻塞时也不被改变)

Java中通过同步机制来解决安全问题

同步方式一

声明同步代码块;

关键字:synchronized

格式:

使用锁(同步监视器)Synchronized(锁){
//需要被同步的代码(多个线程共同操作共享数据的代码)}

锁:任何一个类的对象都可作为锁;

要求:多个线程需要共用一把锁;

同步方式二

如果操作共享数据的代码完整地声明在一个方法中,可以直接将方法声明为同步的;

public synchronized void add(){
}//相当于synchronized(this){},如果是静态同步方法相当于synchronized(xxx.class)

注意:

1.同步方法仍然涉及到锁,只是不需要显式声明;

2.非静态的同步方法锁是this;

3.静态同步方法锁是当前类本身;

同步方法三

通过显式定义同步锁对象来实现同步,同步锁由Lock类的对象来充当;

步骤

1.实例化ReentrantLock;

2.锁对象调用锁定方法:lock(),手动启动同步,之后由该线程访问的共享数据不被其他线程访问;

3.锁对象调用解锁方法:unlock(),手动结束同步;

(6)线程的通信

线程之间通信的两个基本问题是互斥和同步。

线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

线程互斥是指对于共享的操作系统资源(指的是广义的"资源",而不是Windows的.res文件,譬如全局变量就是一种共享资源),在各线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。

涉及到三个方法:

1.wait():wait()执行以后,线程进入阻塞状态并且释放线程的锁;

2.notify():notify()执行以后,唤醒wait()进入阻塞的一个线程(优先唤醒级别高的线程);

3.notifyAll():唤醒所欲哦wait()阻塞的线程;

注意:

1.三个方法都需要在同步代码块或者同步方法中执行;

2.三个方法的调用者必须是同步代码块或者同步方法的锁;

3.三个方法是Object类中的方法;

sleep()和wait()方法的异同

相同:都会使线程进入阻塞状态;

不同:

sleep()声明在Thread类,wait()声明在Object类;

sleep()可以在任意场景下调用,wait()只用于同步的代码中;

sleep()不会释放锁,wait()会释放锁;

(7)获取线程状态

Java 线程的生命周期中,在 Thread 类里有一个枚举类型 State,定义了线程的几种状态,分别有:

New 初始状态

当创建Thread实例对象以后,线程就进入了初始状态;

Runnable

就绪状态

就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。

调用线程的 start() 方法,此线程进入就绪状态。

当前线程 sleep() 方法结束,其他线程 join() 结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。

当前线程时间片用完了,调用当前线程的 yield() 方法,当前线程进入就绪状态。

锁池里的线程拿到对象锁后,进入就绪状态。

运行中状态

线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

Blocked 阻塞状态

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

Waiting 等待状态

处于这种状态的线程不会被分配 CPU 执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

Timed Waiting 超时等待

处于这种状态的线程不会被分配 CPU 执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。

Terminated 终止状态

当线程的 run() 方法完成时,或者主线程的 main() 方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。

在一个终止的线程上调用 start() 方法,会抛出 java.lang.IllegalThreadStateException 异常。

(8)线程池

线程池是管理线程的容器;

优势

(1)降低资源消耗。线程是一个对象,创建一个对象需要经过类的加载过程,销毁一个对象需要经过GC垃圾回收过程,这些都需要资源开销。通过重复利用已创建的线程可以降低线程创建和销毁造成的消耗。

(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池状态

//ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:volatile int runState;//状态static final int RUNNING = 0;//当创建线程池后,初始时,线程池处于RUNNING状态;static final int SHUTDOWN = 1;//如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;static final int STOP = 2;//如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;static final int TERMINATED = 3;//当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

相关的API

Executors//工具类,线程池的工厂类,用于创建并返回不同类型的线程ExecutorService//线程池的接口,常见子类ThreadPoolExecutor

Executors中创建线程池的静态方法

public static ExecutorService newFixedThreadPool()//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源进行设置,如Runtime.getRuntime().availableProcessors()    public static ExecutorService newSingleThreadExecutor()//创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO/LIFO/优先级)执行。 public static ExecutorService newCachedThreadPool()//创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。 public static ScheduledExecutorService newScheduledThreadPool()//创建一个定长线程池,支持定时及周期性任务执行。    /*线程工厂(threadFactory)线程工厂指定创建线程的方式,需要实现ThreadFactory接口,并实现newThread(Runnable r)方法。该参数可以不用指定,Executors框架已经为我们实现了一个默认的线程工厂**/static class DefaultThreadFactory implements ThreadFactory

ThreadPoolExecutor

线程池的实现类;

public class ThreadPoolExecutor extends AbstractExecutorService//该类继承于AbstractExecutorService抽象父类public abstract class AbstractExecutorService implements ExecutorService//AbstractExecutorService父类实现ExecutorService接口

类中有四种构造器

public ThreadPoolExecutor(int corePoolSize,                          int maximumPoolSize,                          long keepAliveTime,                          TimeUnit unit,                          BlockingQueue
workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);} public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
workQueue, ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler);} public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
workQueue, RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);} public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler;}

核心参数

  • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。
  • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。
  • unit(必需):指定keepAliveTime参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  • workQueue(必需):任务队列。通过线程池的execute()方法提交的Runnable对象将存储在该参数中。其采用阻塞队列实现。
  • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
  • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。主要有四种类型

线程池的创建

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

//1.创建线程池ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,                                             MAXIMUM_POOL_SIZE,                                             KEEP_ALIVE,                                             TimeUnit.SECONDS,                                             sPoolWorkQueue,                                             sThreadFactory);//2.向线程池提交任务threadPool.execute(new Runnable() {
@Override public void run() {
... // 线程执行的任务 }});//3.关闭线程池threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

线程池运行实例

public class ThreadPoolTest {
public static void main(String[] args) {
ThreadFactory threadFactory=Executors.defaultThreadFactory();//线程工厂实例 BlockingQueue blockingQueue=new ArrayBlockingQueue(2);//任务队列实例 ThreadPoolExecutor threadPool=new ThreadPoolExecutor(2,4,10,TimeUnit.SECONDS,blockingQueue,threadFactory); threadPool.execute(new Runnable() {
@Override public void run() {
for(int i=0;i<50;i++){
try {
Thread.sleep(300); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println(i); } } }); threadPool.execute(new Runnable(){
@Override public void run() {
for(int i=50;i<100;i++){
try {
Thread.sleep(300); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println(i); } } }); threadPool.execute(new Runnable(){
@Override public void run() {
for(int i=100;i<150;i++){
try {
Thread.sleep(300); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println(i); } } }); threadPool.shutdown(); }}

线程池的执行流程

对应ThreadPoolExecutor类的**execute()**方法

//ctl是一个32位的线程池信息标识变量。//包含两个概念://workerCount:表明当前有效的线程数//runState:表明当前线程池的状态,是否处于Running,Shutdown,Stop,Tidying,Terminate五种状态。public void execute(Runnable command) {
//提交一个任务 if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) {
//线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务; if (addWorker(command, true)) return; c = ctl.get(); } /*如果线程池核心线程已满,新提交的任务会被放进任务队列等待执行; *当任务队列已经满了,判断线程数是否达到最大线程数,如果没有,创建一个非核心线程执行提交的任务; *如果当前的线程数达到了最大线程数,则采用拒绝策略处理; **/ if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command);//不能进入任务队列排队,则判断能否添加一个新线程,如果失败,就知道已关闭或饱和,采用拒绝策略处理}
img

addWorker源码解析

private boolean addWorker(Runnable firstTask, boolean core) {
retry: for (;;) {
int c = ctl.get();//获取当前线程池的状态 int rs = runStateOf(c); //如果线程池状态是STOP、TIDYING、TERMINATED,返回false; //如果线程池状态是SHUTDOWN但是firstTask不为空或者workQueue为空,直接返回false; if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) {
//循环 int wc = workerCountOf(c);//获取当前工作线程的数量 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))//判断是否超过核心线程数或者最大线程数 return false; if (compareAndIncrementWorkerCount(c)) break retry;//如果线程数符合要求,通过CAS算法,将WorkCount加一,跳出retry自旋 c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false;//线程启动标志 boolean workerAdded = false;//线程添加进集合works标志 Worker w = null; try {
w = new Worker(firstTask); final Thread t = w.thread; if (t != null) {
final ReentrantLock mainLock = this.mainLock;//获取线程池的重入锁 mainLock.lock(); try {
// Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get());//获取线程池状态 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // 判断状态是否满足 throw new IllegalThreadStateException(); workers.add(w);//将worker对象加入到workers集合 int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally {
mainLock.unlock(); } if (workerAdded) t.start();//启动线程开始执行任务 workerStarted = true; } } } finally {
if (! workerStarted) addWorkerFailed(w);//线程启动失败,执行addWorkerFailed方法 } return workerStarted;}/*执行流程:先判断线程池状态是否满足,再判断工作中的线程数量是否满足(小于coreSize或者maximumPoolSize),都满足就将执行任务添加到工作任务集合,并启动执行该线程。workers是一个HashSet集合,由coreSize/maximumPoolSize控制**/

四种拒绝策略

当线程池的线程数达到最大线程数时,需要执行拒绝策略。拒绝策略需要实现RejectedExecutionHandler接口,并实现**rejectedExecution(Runnable r, ThreadPoolExecutor executor)**方法。不过Executors框架已经为我们实现了4种拒绝策略:

AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。

CallerRunsPolicy:由调用线程处理该任务。

DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。

DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。

任务队列(workQueue)

任务队列是基于阻塞队列实现的,即采用生产者消费者模式,在Java中需要实现BlockingQueue接口。但Java已经为我们提供了7种阻塞队列的实现:

ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。

LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为Integer.MAX_VALUE

PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现Comparable接口也可以提供Comparator来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。

DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现Delayed接口,通过执行时延从队列中提取任务,时间没到任务取不出来。

SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用take()方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用put()方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。

LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样FIFO(先进先出),也可以像栈一样FILO(先进后出)。

LinkedTransferQueue: 它是ConcurrentLinkedQueueLinkedBlockingQueueSynchronousQueue的结合体,但是把它用在ThreadPoolExecutor中,和LinkedBlockingQueue行为一致,但是是无界的阻塞队列。

注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置maximumPoolSize没有任何意义。

线程池异常处理

在使用线程池处理任务的时候,任务代码可能抛出RuntimeException,抛出异常后,线程池可能捕获它,也可能创建一个新的线程来代替异常的线程,我们可能无法感知任务出现了异常,因此我们需要考虑线程池异常情况。

1.使用try-catch捕获异常;

2.定义在AbstractExecutorService类中的submit方法

/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException       {@inheritDoc} */public 
Future
submit(Runnable task, T result) {
if (task == null) throw new NullPointerException(); RunnableFuture
ftask = newTaskFor(task, result); execute(ftask); return ftask;}/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */public
Future
submit(Callable
task) { if (task == null) throw new NullPointerException(); RunnableFuture
ftask = newTaskFor(task); execute(ftask); return ftask;}

由submit()方法执行的任务,可以通过Future对象的get方法接收抛出的异常,再进行处理。

3.为工作者线程设置UncaughtExceptionHandler,在uncaughtException方法中处理异常

image-20201128004850812

4.重写ThreadPoolExecutor的afterExecute方法,处理传递的异常引用

image-20201128004918131

newFixedThreadPool线程池导致的内存飙升问题

public static ExecutorService newFixedThreadPool(int nThreads) {
//线程池构造方法 return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
());}//阻塞队列是由LinkedBlockingQueue
()无参构造器生成的对象

LinkedBlockingQueue是使用单向链表实现的,类中无参构造器的定义如下

public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);//默认构造Integer.MAX_VALUE大小的链表;}

可知,LinkedBlockingQueue由无参构造器创建的队列对象会将所有任务加入任务队列,几乎无界,而newFixedThreadPool线程池核心线程数固定,因此当任务执行时间较长没有及时释放阻塞队列,会导致任务堆积越来越多,内存使用飙升,造成JVM出现OOM。

(9)题目

1.run方法和start方法的区别?

执行run()不会创建线程,执行start()创建新的线程然后执行run()方法中的代码

2.怎样保证线程安全?

通过合理的时间调度,避免共享资源的存取冲突;

通过同步或者锁,保证同一时间共享资源只会被一个线程操作;

通过适当的策略进行资源的分配,保证任务与人物之间不存在共享资源;

3.线程的基本状态与状态之间的关系?

新建Thread类或者其子类——新建状态;

调用start()方法——就绪状态;

新建的线程中执行run()方法中的代码——运行状态;

sleep()、join()、wait()方法的调用——阻塞状态;

线程执行结束或者其他终止策略——死亡状态;

4.什么是线程池?

线程池是一个创建、管理、调度线程的容器;

5.举例说明同步和异步?

同步:操作共享数据时,为避免正在修改的数据被其他线程操作,则需要进行同步处理;

异步:当应用程序执行一个较长时间的代码时不想等待其完成而是同时执行其他任务,就要用到异步编程;

6.当一个线程进入一个对象的synchronized方法A时,不能进入同一对象的synchronized方法B:因为非静态方法的锁就是对象本身,当在方法A中用到锁,则需要等待释放;

7.sleep()方法与yield()方法的区别?

sleep()方法给其他线程的运行机会不考虑优先级,执行以后进入阻塞,需要抛出异常;而yield()方法只会给相同优先级或更高优先级的线程运行机会,执行后线程进入就绪状态,没有声明任何异常;

8.实现线程的方法

继承Thread类、实现Runnable接口、实现Callable接口、创建线程池;

9.为什么不推荐使用stop()和suspend()方法?

stop()方法不安全,它会释放锁;suspend()方法容易发生死锁,调用后目标线程会停下来,但仍然保持之前获得的锁定,对于其他线程来说,它们需要恢复目标线程,又需要操作被锁的共享资源时,就造成死锁;

10.线程池的优势?

降低资源损耗,可以重复利用线程,省去多余的创建和销毁开销;

提高响应速度,若池中有空闲线程,可直接调度而不需创建;

提高线程的可管理性;

11.Java有哪几种线程池?

newFixedThreadPool()//指定工作线程数量的线程池

newSingleThreadExecutor()//单例线程的线程池
newCachedThreadPool()//可缓存线程池,工作线程的创建没有限制;
newScheduledThreadPool()//定长线程池,支持定时和周期的任务执行;

12.i++线程安全吗?

不安全,i++先读取i值,然后对i+1,再将值赋给i,其中任意一步都可能被其他线程抢占;

转载地址:http://srqzi.baihongyu.com/

你可能感兴趣的文章
跨平台Java程序注意事项
查看>>
Python字符与数字的相互转换
查看>>
C 指针解读
查看>>
有关乱码的处理---中国程序员永远无法避免的话题
查看>>
JSP的运行内幕
查看>>
python超简单的web服务器
查看>>
代理模式、静态代理、动态代理、aop
查看>>
Struts1.x Spring2.x Hibernate3.x DWR2.x整合工具文档v1.00
查看>>
大型Web2.0站点构建技术初探
查看>>
机器学习算法汇总:人工神经网络、深度学习及其它
查看>>
解决Spring中AOP不能切入Struts的DispatchAction方法的问题
查看>>
出国以后才知道英语应该怎么学
查看>>
计算机专业权威期刊投稿经验总结
查看>>
如何在三个月内学会一门外语?
查看>>
看看你对Linux到底了解多少?
查看>>
网上看到的:ARM入门最好的文章(转)
查看>>
中国最美情诗100句
查看>>
javascript注册window的onload事件问题研究
查看>>
客户端技术分页控件javascript+css,可用于任何服务器端技术
查看>>
学习Swing 的网站[转]
查看>>