【校招VIP】多线程面试题汇总

08月04日 收藏 0 评论 0 java开发

【校招VIP】多线程面试题汇总

转载声明:原文链接:https://blog.csdn.net/weixin_35802502/article/details/81143482

1、线程的基本概念、线程的基本状态与状态之间的关系?
线程是进程里面一个执行上下文,或者是执行序列。线程是进程级别上的多道编程。同一进程的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈。线程之间切换快,无需陷入内核态。
线程状态

阻塞状态分分为三种:等待阻塞(wait)、同步阻塞( 加锁 )、其他阻塞(睡眠)
新建状态
当用new操作符创建一个线程时。此时程序还没有开始运行线程中的代码。
就绪状态
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序来调度的。
运行状态(runing)
当线程获得 CPU时间后,它才进图运行状态,真正开始执行run()方法。
阻塞状态(blocked)
线程运行过程中,可能由于各种原因进入阻塞状态:
①线程通过调用sleep方法进入睡眠状态;
②线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
③线程试图得到一个锁,而该锁正被其他线程持有;
④线程在等待某个触发条件;
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
死亡状态(dead)
有两个原因会导致线程死亡:
①run方法正常退出而自然死亡;
②一个未捕获的异常终止了run方法而使线程猝死;
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法,如果是可运行或被阻塞,这个方法返回true;如果线程仍旧是new状态且不是可运行的,或者线程死亡了,则返回false。

2、进程与线程的区别:
一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其他线程,多个线程并发地运行在同一个进程。一个进程汇总的所有线程都在该进程的虚拟地址空间汇总,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。
基于进程的多任务处理是程序的并发执行。基于线程的多任务处理是同一程序的片段的并发执行;
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间;
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的;
一个进程奔溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃则整个进程都死掉,所以多进程要比多线程健壮;
进程切换时,消耗资源大,效率高,所以涉及到频繁切换时,使用线程要好于进程;
执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存于应用程序中,有应用程序提供多个线程执行控制。
线程是处理器调度的基本单位,但进程不是。

3、多线程有几种实现方法
C++创建多线程的三种方式:
1.CreateThread()
CreateThread()是windows的API函数,提供操作系统级别的创建线程的操作,且仅限于工作者线程。不调用MFC和RTL函数(标准C语言函数),即只调用win32API时,可以 用CreateThread。在使用过程中要考虑到进程捡的同步与互斥关系(防止死锁)。
线程函数定义为: DWORD WINAPI _yourThreradFun(LPVOID)
头文件 <windows.h>
创建线程API:

1 HANDLE CreateThread(
2 __in SEC_ATTRS
3 SecurityAttributes, //线程安全属性
4 __in ULONG
5 StackSize, // 堆栈大小
6 __in SEC_THREAD_START
7 StartFunction, // 线程函数
8 __in PVOID
9 ThreadParameter, // 线程参数
10 __in ULONG
11 CreationFlags, // 线程创建属性
12 __out PULONG
13 ThreadId // 线程ID
14 );

使用步骤:

HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
CloseHandle(hThread);

2、互斥API:互斥量实现

1 HANDLE CreateMutex(
2 LPSECURITY_ATTRIBUTES lpMutexAttributes,
3 BOOL bInitialOwner, // 指定该资源初始是否归属创建它的进程
4 LPCTSTR lpName // 指定资源的名称
5 );
6
7 BOOL ReleaseMutex(
8 HANDLE hMutex // 该函数用于释放一个独占资源,进程一旦释放该资源,该资源就不再属于它了
9 );
10
11 DWORD WaitForSingleObject(
12 HANDLE hHandle, // 指定所申请的资源的句柄
13 DWORD dwMilliseconds // 一般指定为INFINITE,表示如果没有申请到资源就一直等待该资源
14 );

3、互斥API:临界区实现

1 CRITICAL_SECTION cs;
2 InitializeCriticalSection(&cs);
3 EnterCriticalSection(&cs);
4 LeaveCriticalSection(&cs);
5 DeleteCriticalSection(&cs);

例子:

1 #include <iostream>   
2 #include <windows.h>
3 using namespace std;
4
5 HANDLE hMutex;
6
7 DWORD WINAPI Fun(LPVOID lpParamter)
8 {
9 while (1) {
10 WaitForSingleObject(hMutex, INFINITE);
11 cout << "Fun display!" << endl;
12 Sleep(1000);
13 ReleaseMutex(hMutex);
14 }
15 }
16
17 int main()
18 {
19 HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
20 hMutex = CreateMutex(NULL, FALSE, "screen");
21 CloseHandle(hThread);
22 while (1) {
23 WaitForSingleObject(hMutex, INFINITE);
24 cout << "main display!" << endl;
25 Sleep(2000);
26 ReleaseMutex(hMutex);
27 }
28
29 return 0;
30 }

4、多线程同步和互斥实现方式
当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。
所谓同步,是指在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
所谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
实现方式:
线程间的同步方法大体可分为两类:用户模式和内核模式。内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。
内核模式下的方法有:事件,信号量,互斥量。
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问(可能造成竞争的共享资源)。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
3、信号量:为控制一个具有有限数量用户资源而设计。
4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
同步的目的:多线程程序 的执行结果有可能是不确定的---》为了消除不确定,产生了线程同步,即就是不管线程之间的执行如何穿梭,其运行结果都是正确的。

5、多线程同步和互斥有何异同,在什么情况下分别使用它们?
线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步(下文统称为同步)。
线程同步解决多线程并发问题:

几种内核对象中,除了互斥量,没有任何一个会记住自己是哪个线程等待成功的。
互斥量和关键段的比较:
(1)关键段只适用于同一个进程,互斥量可适用与不同的进程
(2)关键段的效率更高
(3)关键段不安全,而互斥量更安全;
假如把整条道路看成是一个【进程】的话,那么马路中间白色虚线分隔开来的各个车道就是进程中的各个【线程】了。
  ①这些线程(车道)共享了进程(道路)的公共资源(土地资源)。
  ②这些线程(车道)必须依赖于进程(道路),也就是说,线程不能脱离于进程而存在(就像离开了道路,车道也就没有意义了)。
  ③这些线程(车道)之间可以并发执行(各个车道你走你的,我走我的),也可以互相同步(某些车道在交通灯亮时禁止继续前行或转弯,必须等待其它车道的车辆通行完毕)。
  ④这些线程(车道)之间依靠代码逻辑(交通灯)来控制运行,一旦代码逻辑控制有误(死锁,多个线程同时竞争唯一资源),那么线程将陷入混乱,无序之中。
  ⑤这些线程(车道)之间谁先运行是未知的,只有在线程刚好被分配到CPU时间片(交通灯变化)的那一刻才能知道。
  注:
  由于用于互斥的信号量sem与所有的并发进程有关,所以称之为公有信号量。公有信号量的值反映了公有资源的数量。只要把临界区置于P(sem)和V(sem)之间,即可实现进程间的互斥。就象火车中的每节车厢只有一个卫生间,该车厢的所有旅客共享这个公有资源:卫生间,所以旅客间必须互斥进入卫生间,只要把卫生间放在P(sem)和V(sem)之间,就可以到达互斥的效果。

C 0条回复 评论

帖子还没人回复快来抢沙发