1.什么是线程?
概念:是程序执行流的最小单元。
组成:一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成
状态:由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态
周期:新建 就绪 运行 阻塞 死亡
2.多线程有什么用?
可以提高效率:涉及到CPU和io等待
如果一个线程在执行的时候遇到了阻塞(io等待),那么这个时候CPU会很闲,如果你设置了多线程的话,CPU可以在很闲的这个时间去处理其它线程,提高CPU的利用率。当然,CPU在不同的线程之间进行切换也是需要时间的,但是时间比较短,一般可以忽略
举例子:某银行处理数据同步的问题,每天凌晨银行那边会有几十万条数据发送过来进行处理,几百万条数据接收后直接去处理了,每次运行大多都要耗费2.5个小时,时间实在太久,如果哪天有几亿数据是不是要跑几十个小时了?那么就可以用多线程,切割这个数据,利用不同的线程来处理,最后把时间缩短到了半小时左右
创建:Java中创建线程主要有三种方式:
①继承Thread类创建线程类,并重写该类的run方法,该run()方法的方法体就代表了线程要完成的任务。调用线程对象的start()方法来启动该线程。
②实现runnable接口,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。调用线程对象的start()方法来启动该线程。
③实现Callable接口,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。创建一个Callable实现类的实例对象,再使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
创建线程的三种方式的对比:
采用实现Runnable、Callable接口的方式创见多线程时
优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时
优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
3.在java中wait和sleep方法的不同?
回答这个问题之前先了解线程的几种主要状态:
线程主要有以下几种状态:
就绪(Runnable):线程准备运行,不一定立马就能开始执行。
运行中(Running):进程正在执行线程的代码。
等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。
睡眠中(Sleeping):线程被强制睡眠。
I/O阻塞(Blocked on I/O):等待I/O操作完成。
同步阻塞(Blocked on Synchronization):等待获取锁。
死亡(Dead):线程完成了执行。
sleep()方法是来自Thread类的方法,因此它不能改变对象的锁。所以当在一个Synchronized方法中调用sleep()时,线程虽然休眠了,但是对象的锁没有被释放,其他线程仍然无法访问这个对象。
wait()方法则是来自Object类的方法,它可以改变对象的锁,会在线程休眠的同时释放掉锁,其他线程可以访问该对象。
4.为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
当调用start()方法时,系统才会开启一个线程,通过Thead类中start()方法来启动的线程处于就绪状态(可运行状态),此时并没有运行,一旦得到CPU时间片,就自动开始执行run()方法。
而run()方法并没有开启一个线程,只是线程里的一个函数,可以把run()方法看成是一个普通的方法,是继承了Thread类之后我们自己重写的方法。
区别就是:start() 创建新进程 ,run() 没有
5.线程与进程的区别?
首先了解进程执行的背景:CPU的速度非常的块,如果有许多进程等待处理,对于CPU来说就是每个进程轮流这来,当进程得到CPU的时候,相关的资源必须也已经就位,比如显卡、GPS什么的必须已经就位,然后CPU开始执行。这里除了CPU以外所有的就构成了这个程序的执行环境,也就是我们所定义的程序上下文。当这个程序执行完了,或者分配给他的CPU执行时间用完了,那它就要被切换出去,等待下一次CPU的临幸。在被切换出去的最后一步工作就是保存程序上下文,因为这个是下次他被CPU运行的运行环境,必须保存。
进程就是包括上下文切换的程序执行时间总和 = CPU加载上下文+CPU执行+CPU保存上下文
在CPU执行这个进程的时候,不会是一个逻辑进行到底的,就好比要实现程序甲,实际分成 a,b,c等多个块组合而成。那么这里具体的执行就可能变成:程序甲得到CPU =》CPU加载上下文,开始执行程序甲的a小段,然后执行程序甲的b小段,然后再执行程序甲的c小段,最后CPU保存程序甲的上下文。执行完毕。
这里a,b,c的执行是共享了程序甲的上下文,CPU在执行的时候没有进行上下文切换的。这里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境。
区别:
1) 一个程序至少有一个进程,一个进程至少有一个线程.
2) 线程的划分尺度小于进程,可以看成是进程的一部分。
3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
4) 线程在执行过程中与进程还是有区别的。每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。
6.在Java中什么是线程调度?
一般来说,计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令.所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务.在运行池中,会有多个处于就绪状态的线程在等待CPU,JAVA虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配CPU的使用权.
7.在线程中你怎么处理不可捕捉异常?
先说一下怎么出现的:异常在线程中是不能共享的,main函数也是一个线程名称叫做主线程,它与线程池里面的线程是相互独立的,就是说你发现一个线程报错了,这个报错信息是由子线程报出。但是这个线程跟主线程无关,并没有抛出异常到主线程上去。所以主线程的捕获无法捕获其他子线程的异常,它根本不知道已经报了异常。线程运行是互相独立的,可以理解主线程也是一种普通的线程即可。如果线程之间异常互相干扰,那么1000个线程,一个线程挂了,其它线程跟着遭殃,这是不合理的。
怎么处理:如果一定要主线程去 catch 子线程的异常,可以使用
thread.setUncaughtExceptionHandler(new ExceptionHandler());
8.如何在Java中实现线程?用Runnable还是Thread?
参见上面第2题,三种创建线程方式的优缺点
9.Thread 类中的start() 和 run() 方法有什么区别?
区别就是:start() 创建新进程 ,run() 没有
10.什么是线程池?为什么要使用它?
线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。也可以自己设置线程数量。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。
线程池的结构:
1、线程池管理器(ThreadPoolManager):用于创建并管理线程池
2、工作线程(WorkThread): 线程池中线程
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
4、任务队列:用于存放没有处理的任务。提供一种缓冲机制
为什么使用:(了解前四个)
1. 改进了一个应用程序的响应时间,应用程序可以直接拿来使用而不用新建一个线程。
2. 线程池节省创建一个完整的线程的开销并可以在任务完成后回收资源。
3. 线程池根据当前在系统中运行的进程来优化线程时间片。
4. 线程池允许我们开启多个任务而不用为每个线程设置属性。
5. 线程池允许我们为正在执行的任务的程序参数传递一个包含状态信息的对象引用。
6. 线程池可以用来解决处理一个特定请求最大线程数量限制问题。