1.什么是线程和进程,区别是什么?
进程:进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
线程:线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。
区别:
-
进程由线程组成。进程之间基本上是相互是独立的,而同一进程中的线程极有可能相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
-
一个进程中可以有多个线程,多个线程共享进程的堆和方法区,但是每个线程有自己的程序计数器、虚拟机栈和本地方法栈。
2.多线程可能会带来什么问题
并发编程的目的是为了能提高程序的执行效率。但是,并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等。
3.如何理解线程安全和不安全?
线程是否安全是指多线程环境下对于同一份数据的访问是否能够保证其正确性和一致性。
-
线程安全指的是在多线程环境下,对于同一份数据,不管有多少个线程同时访问,都能保证这份数据的正确性和一致性。
-
线程不安全则表示在多线程环境下,对于同一份数据,多个线程同时访问时可能会导致数据混乱、错误或者丢失。
4.什么是多线程中的上下文切换?
上下文切换是指 CPU 从一个线程转到另一个线程时,需要保存当前线程的上下文状态,恢复另一个线程的上下文状态,以便于下一次恢复执行该线程时能够正确地运行。当出现如下情况的时候,线程会从占用 CPU 状态中退出,进行上下文切换。
-
主动让出 CPU,比如调用了 sleep()、wait() 等。
-
时间片用完,因为操作系统要防止一个线程长时间占用 CPU 导致其他线程饿死。
-
调用了阻塞类型的系统中断,比如请求 IO,线程被阻塞。
-
被终止或结束运行。
当线程请求进行IO操作,比如读写文件、网络通信等,由于IO操作通常需要等待外部设备的响应,线程会进入阻塞状态,同时释放CPU资源给其他线程使用。
如果线程频繁进行上下文切换会降低程序执行的性能。
5.并发和并行的区别
-
并行:可以理解为在单核 CPU 上同时运行多个程序,并发并不是真正意义上的同时进行,而是通过抢占 CPU 时间片去执行。
-
并发:可以理解为在多个 CPU 或者一个多核 CPU 上同时运行多个程序,每个 CPU 或者多核 CPU 可以分别运行程序,二个程序互不抢占CPU资源,可以真正做到同时进行。
6.同步和异步的区别
-
同步:发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
-
异步:调用在发出之后,不用等待返回结果,该调用直接返回。
7.说说Java中线程的生命周期?
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:
初始状态(NEW):线程被创建完成,但是还没有调用 start()。
运行状态(RUNNABLE):Java线程中将就绪和运行中两种状态笼统的称为“运行”。
-
就绪(READY):线程对象创建后,其他线程调用了该线程对象的 start() 方法。该状态的线程位于可运行线程池中,等待被线程调度选中并分配 cpu 使用权 。
-
运行中(RUNNING):就绪状态的线程获得了cpu 时间片,开始执行程序代码。
阻塞状态(BLOCKED):线程需要等待某个资源上锁的释放。
等待状态(WAITING):表示该线程需要等待其他线程做出通知或中断等特定动作。
超时等待状态(TIME_WAITING):相当于在等待状态的基础上增加了超时限制。当超时时间结束后,线程将会返回到 RUNNABLE 状态。
终止状态(TERMINATED):表示该线程已经运行完毕。
Java 中通过 sleep(long millis)方法或 wait(long millis)方法可以将线程置于 TIMED_WAITING 状态。
8.什么是线程死锁?如何避免死锁?
死锁:死锁是指多个线程都在等待对方释放自己所需要的资源而无法继续执行。
如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
产生死锁的四个必要条件:
-
互斥条件:该资源任意一个时刻只由一个线程占用。
-
请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
-
不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
-
循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
如何预防死锁?
破坏死锁的产生的必要条件即可:
-
破坏请求与保持条件:一次性申请所有的资源。
-
破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
-
破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
9.sleep方法和wait方法对比
共同点:两者都可以暂停线程的执行。
区别:
-
sleep() 是 Thread 类的静态本地方法;wait() 则是 Object 类的本地方法。
-
sleep() 方法可以在任何地方使用;而wait()方法则只能在同步方法或同步块中使用。
-
sleep() 方法没有释放锁;而 wait() 方法释放了锁 。
-
sleep() 通常被用于暂停执行;wait() 通常被用于线程间交互/通信。
-
sleep() 方法执行后,线程会根据指定的时间自动苏醒;wait() 方法被调用后,线程不会自动苏醒,必须等其它线程调用同一个对象上的 notify()/notifyAll() 方法来唤醒它。
当一个线程调用了wait()方法进入了等待状态,它会释放某个对象的锁并暂停执行,等待其他线程通过调用相同对象上的notify()或notifyAll()方法,才会从等待队列中被移出。
wait(long timeout) 方法是wait()方法的另一个变体,它在等待一定时间后会自动苏醒。即使没有其他线程调用相同对象上的notify()或notifyAll()方法。
10.为什么wait方法不定义在Thread中?
wait() 是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象(Object)都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入 WAITING 状态,自然是要操作对应的对象(Object)而非当前的线程(Thread)。
11.可以直接调用Thread类的run方法吗?
不可以,调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。
new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
12.notify和notifyAll的区别
当一个线程进入wait之后,就必须等其他线程notify()/notifyAll(),才会从等待队列中被移出。使用notifyAll,可以唤醒所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个。