目录
1、线程和进程的关系和区别
2、run和start方法的区别
start()
run()
区别
3、sleep和wait
4、Java中实现多线程有几种方法
1、继承Thread类
2、实现runnable接口
3、实现callable接口
5、线程的几种状态
6、Callable 和Runnable 的区别是什么
7、volatile 的理解
8、Java 中notify 和 notifyAll 有什么区别
9、什么是乐观锁和悲观锁
10、线程池的作用
11、产生死锁的条件
12、线程阻塞有哪些原因?
13、synchronized 和 Lock 的区别
14、ThreadLocal 是什么,有什么作用
15、怎么唤醒一个阻塞的线程
16、什么是线程局部变量
17、线程池的几个重要参数?
18、线程池的类型
1、线程和进程的关系和区别
每个进程都有相应的线程,在执行程序时,实际上是执行相应的一系列线程。进程是资源分配的最小单位,线程是程序执行的最小单位。
根本区别:进程是资源分配最小单位,线程是程序执行的最小单位。 计算机在执行程序时,会为程序创建相应的进程,进行资源分配时,是以进程为单位进行相应的分配。每个进程都有相应的线程,在执行程序时,实际上是执行相应的一系列线程。
地址空间:进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段;线程没有独立的地址空间,同一进程的线程共享本进程的地址空间。
资源拥有:进程之间的资源是独立的;同一进程内的线程共享本进程的资源。
执行过程:每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程是处理机调度的基本单位,但是进程不是。由于程序执行的过程其实是执行具体的线程,那么处理机处理的也是程序相应的线程,所以处理机调度的基本单位是线程。
系统开销:进程执行开销大,线程执行开销小。
2、run和start方法的区别
start()
用 start方法来启动线程,是真正实现了多线程, 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法。但要注意的是,此时无需等待run()方法执行完毕,即可继续执行下面的代码。所以run()方法并没有实现多线程。
run()
run()方法只是类的一个普通方法,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码。
区别
1、线程中的start()方法和run()方法的主要区别在于,当程序调用start()方法,将会创建一个新线程去执行run()方法中的代码。但是如果直接调用run()方法的话,会直接在当前线程中执行run()中的代码,注意,这里不会创建新线程。这样run()就像一个普通方法一样。
2、另外当一个线程启动之后,不能重复调用start(),否则会报IllegalStateException异常。但是可以重复调用run()方法。
还有其他区别:
1、start方法用来启动相应的线程;
2、run方法只是thread的一个普通方法,在主线程里执行;
3、需要并行处理的代码放在run方法中,start方法启动线程后自动调用run方法;
4、run方法必去是public的访问权限,返回类型为void。
3、sleep和wait
sleep方法属于Thread类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在sleep的过程中有可能被其他对象调用它的interrupt(), 产生InterruptedException异常,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED结束状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。
wait属于Object的成员方法,一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该线程;如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法也同样会在wait的过程中有可能被其他对象调用interrupt() 方法而产生InterruptedException,效果以及处理方式同sleep()方法。
4、Java中实现多线程有几种方法
1、继承Thread类
1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
2)创建Thread子类的实例,即创建了线程对象。
3)调用线程对象的start()方法来启动该线程。
2、实现runnable接口
1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
3)调用线程对象的start()方法来启动该线程
3、实现callable接口
1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
5、线程的几种状态
1.新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2.就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3.运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4.阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5.死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
6、Callable 和Runnable 的区别是什么
两者都能用来编写多线程,但实现 Callable 接口的任务线程能返回执行结果,而实现 Runnable 接口的任务线程不能返回结果.Callable通常需要和Future/FutureTask结合使用,用于获取异步计算结果。
7、volatile 的理解
volatile 关键字的两层语义 一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备 了两层语义:
1) 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这 新值对其他线程来说是立即可见的。
2) 禁止进行指令重排序。
用 volatile 修饰之后,变量的操作:
第一:使用 volatile 关键字会强制将修改的值立即写入主存;
第二:使用 volatile 关键字的话,当线程 2 进行修改时,会导致线程 1 的工作内存中缓存变 量 stop 的缓存行无效(反映到硬件层的话,就是 CPU 的 L1 或者 L2 缓存中对应的缓存行无 效); 第三:由于线程 1 的工作内存中缓存变量stop 的缓存行无效,所以线程 1 再次读取变量 stop 的值时会去主存读取。
8、Java 中notify 和 notifyAll 有什么区别
notify()方法不能唤醒某个具体的线程,所以只有一个线程在等 待的时候它才有用武之地。 而 notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。
9、什么是乐观锁和悲观锁
1) 乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁 认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作 尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
2) 悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观 锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像 synchronized,不管三七二十一,直接上了锁就操作资源了。
10、线程池的作用
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一 个进程能创建的线程数有限。为了避免这些问题,在程序启动的时 候就创建若干线程来响应 处理,它们被称为线程池,里面的线程叫工作线程。从 JDK1.5 开始,Java API 提供了 Executor框架 让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是 缓存线程池(一个适合很多生存期短 的任务的程序的可扩展线程池)。
11、产生死锁的条件
1、互斥条件:一个资源每次只能被一个进程使用。
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3、不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
12、线程阻塞有哪些原因?
1、sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻 塞状态,不能得到 CPU 时间,指定的时间一过,线程重新进入可执行状态。典型地,sleep()被用 在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满 足为止
2、suspend() 和 resume() 两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会 自动恢复,必须其对应的 resume() 被调用,才能使得线程重新进入可执行状态。典型地, suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生 后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。
3、yield() 使当前线程放弃当前已经分得的 CPU 时间,但不使当前线程阻塞,即线程仍处于可 执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程 已执行了足够的时间从而转到另一个线程。
4、wait() 和 notify() 两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式, 一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用。
13、synchronized 和 Lock 的区别
主要相同点:Lock 能完成 synchronized 所实现的所有功能
主要不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能。synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且必须在 finally 从句中释放。
14、ThreadLocal 是什么,有什么作用
ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。
简单说 ThreadLocal 就是一种以空间换时间的做法,在每个 Thread 里面维护了一个以开地址法实现的 ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安 全方面的问题了。
15、怎么唤醒一个阻塞的线程
如果线程是因为调用了 wait()、sleep()或者 join()方法而导致的阻塞,可以中断线程,并且通 过抛出 InterruptedException 来唤醒它;如果线程遇到了 IO 阻塞,无能为力,因为 IO 是操作 系统实现的,Java 代码并没有办法直接接触到操作系统。
16、什么是线程局部变量
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java 提 供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期 比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java应用就存在内存泄露的风险。
17、线程池的几个重要参数?
corePoolSize 核心线程数
maximumPoolSize 最大线程数,一般大于等于核心线程数
keepAliveTime 线程存活时间(针对最大线程数大于核心线程数时,非核心线程)
unit 存活时间单位,和线程存活时间配套使用
workQueue 任务队列
threadFactory 创建线程的工程
handler 拒绝策略
18、线程池的类型
五种线程池: ExecutorService threadPool = null; threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
threadPool = Executors.newScheduledThreadPool(2);
threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作 threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多



