多线程在笔试面试中经常出现,下面列出一些公司的多线程笔试面试题。首先是一些概念性的问答题,这些是多线程的基础知识,经常出现在面试中的第一轮面试(我参加2011年腾讯研究院实习生招聘时就被问到了几个概念性题目)。然后是一些选择题,这些一般在笔试时出现,虽然不是太难,但如果在选择题上花费大多时间无疑会对后面的编程题造成影响,因此必须迅速的解决掉。最后是综合题即难一些的问答题或是编程题。这种题目当然是最难解决了,要么会引来面试官的追问,要么就很容易考虑不周全,因此解决这类题目时一定要考虑全面和细致。
下面就来看看这三类题目吧。
第一题:线程的基本概念、线程的基本状态及状态之间的关系?
基本概念:一个线程是进程的一个顺序执行流。同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。一个进程中可以包含多个线程。
线程的基本状态:
1.新建
2.等待
3.就绪
4.运行状态
处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU,那么任何时刻只会有一个线程处于这个状态。
只有处于就绪状态的线程才有机会转到运行状态。
5.阻塞状态
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才会有机会获得运行状态。
阻塞状态分为三种:
1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
2、同步阻塞:运行的线程在获取对象同步锁时,若该同步锁被别的线程占用,则JVM会把线程放入锁池中。
3、其他阻塞:运行的线程执行Sleep()方法,或者发出I/O请求时,JVM会把线程设为阻塞状态。当Sleep()状态超时、或者I/O处理完毕时,线程重新转入就绪状态。
6.死亡状态
当线程执行完run()方法中的代码,或者遇到了未捕获的异常,就会退出run()方法,此时就进入死亡状态,该线程结束生命周期。
第二题:线程与进程的区别?
一个进程至少有一个线程。线程的划分尺度小于进程,使得多线程程序的并发性高。另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程的区别在于每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用来实现进程的调度和管理以及资源分配。
第三题:多线程有几种实现方法,都是什么?
个人感觉这题就是脑残,不就是几个接口吗,无非就是封装的pthread_create.
第四题:多线程同步和互斥有几种实现方法,都是什么?
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
第五题:多线程同步和互斥有何异同,在什么情况下分别使用他们?举例说明。
线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步(下文统称为同步)。
同步就是明确的执行顺序。互斥就是排他性。
第一题(百度笔试题):
以下多线程对int型变量x的操作,哪几个不需要进行同步:
A. x=y; B. x++; C. ++x; D. x=1;
在多线程中什么时候需要同步:1.只要有人在修改,2.不是原子操作(一个机器指令)。
x=1是1个机器指令。
第二题(阿里巴巴笔试题)
多线程中栈与堆是公有的还是私有的
A:栈公有, 堆私有
B:栈公有,堆公有
C:栈私有, 堆公有
D:栈私有,堆私有
每个线程拥有一个栈和一个程序计数器,这两个是私有的。这个很好理解,如果不是独有的话,那函数压栈的时候,那多线程肯定相互影响了
第一题(台湾某杀毒软件公司面试题):
在Windows编程中互斥量与临界区比较类似,请分析一下二者的主要区别。
只用过互斥量。听说临界区只能用于单进程中的不同线程。
第二题:
一个全局变量tally,两个线程并发执行(代码段都是ThreadProc),问两个线程都结束后,tally取值范围。
inttally = 0;//glable
voidThreadProc()
{
for(inti = 1; i <= 50; i++)
tally += 1;
}
答案:
[50,100]
解析:
tally += 1 分为三条指令:
(1)tally内存进入寄存器;(2)寄存器加1;(3)寄存器数据放回内存tally;
最坏的情况: 就是线程一读tally,线程二也读tally,线程一寄存器加一,线程二寄存器加一,线程一放回tally,线程二放回tally,这种情况虽然二个线程都对tally加1,但显然只加了一次。所以到最后只加50次。
第三题(某培训机构的练习题):
子线程循环 10 次,接着主线程循环 100 次,接着又回到子线程循环 10 次,接着再回到主线程又循环 100 次,如此循环50次,试写出代码。
写了个,但是没验证:
//子线程跑10次,父线程跑10次。循环10次
int whorun = 0; //偶数的时候是子线程跑,单数的时候是父线程跑
pthread_mutex_t pthread_mutex_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t count_nonzero = PTHREAD_COND_INITIALIZER;
void *threadFunc(void *param)
{
int count = 0; //控制跑10次
while(count++ < 10)
{
pthread_mutex_lock (&count_lock);
while(whorun%2 != 0)
pthread_cond_wait(&qready, &mylock);
for(int i = 0; i < 10; ++i)
{
;//随便打
}
whorun++;
pthread_cond_signal( &count_nonzero, &count_lock);
pthread_mutex_unlock (&count_lock);
}
return 0;
}
int main()
{
int count = 0; //控制跑10次
pthread_t pid;
pthread_create(pid,NULL,threadFunc,NULL);
while(count++ < 10)
{
pthread_mutex_lock (&count_lock);
while(whorun%2 == 0)
pthread_cond_wait(&qready, &mylock);
for(int i = 0; i < 10; ++i)
{
;//随便打
}
whorun++;
pthread_cond_signal( &count_nonzero, &count_lock);
pthread_mutex_unlock (&count_lock);
}
pthread_join(pid,NULL);
return 0;
}
第四题(迅雷笔试题):
编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。
#include
#include
#include
#include
#include
//#define DEBUG 1
#define NUM 3
int n=0;
pthread_mutex_t mylock=PTHREAD_MUTEX_INITIALIZER;//互斥量
pthread_cond_t qready=PTHREAD_COND_INITIALIZER;//条件变量
void * thread_func(void *arg)
{
int param=(int)arg;
char c='A'+param;
int ret,i=0;
for (; i < 10; i++)
{
pthread_mutex_lock(&mylock);
while (param != n) //刚运行时,n = 0, param = 0,条件不成立,所以直接打印A
{
#ifdef DEBUG
printf("thread %d waiting\n", param);
#endif
ret = pthread_cond_wait(&qready, &mylock);
if (ret == 0)
{
#ifdef DEBUG
printf("thread %d wait success\n", param);
#endif
} else
{
#ifdef DEBUG
printf("thread %d wait failed:%s\n", param, strerror(ret));
#endif
}
}
// printf("%d ",param+1);
printf("%c ",c); //打印A后
n=(n+1)%NUM; //n变成了1,对线程2会产出影响!!!!
pthread_mutex_unlock(&mylock);
//会唤醒所有的线程,因为当这个线程完后会等pthread_cond_wait()执行两次后才能退出while (param != n)
pthread_cond_broadcast(&qready);
}
return (void *)0;
}
int main(int argc, char** argv) {
int i=0,err;
pthread_t tid[NUM];
void *tret;
for(;i
err=pthread_create(&tid[i],NULL,thread_func,(void *)i);
if(err!=0)
{
printf("thread_create error:%s\n",strerror(err));
exit(-1);
}
}
for (i = 0; i < NUM; i++)
{
err = pthread_join(tid[i], &tret);
if (err != 0)
{
printf("can not join with thread %d:%s\n", i,strerror(err));
exit(-1);
}
}
printf("\n");
return 0;
}
第五题(Google面试题)
有四个线程1、2、3、4。线程1的功能就是输出1,线程2的功能就是输出2,以此类推.........现在有四个文件ABCD。初始都为空。现要让四个文件呈如下格式:
A:1 2 3 4 1 2....
B:2 3 4 1 2 3....
C:3 4 1 2 3 4....
D:4 1 2 3 4 1....
请设计程序。
个人觉得这个也是同步的解法。我会申明4个文件指示器(1-4间变化),以及4个数组,数组里存的就是哪个线程可以打对应的文件。文件几个就建几个数组和文件指示器。线程死循环,每次循环4个判断条件。这里条件变量只要1个就好了。速度慢,但是应该可以实现出来。
下面的第六题与第七题也是在考研中或是程序员和软件设计师认证考试中的热门试题。
--------------------------------------------------------------------------------------------------------
第六题
生产者消费者问题
这是一个非常经典的多线程题目,题目大意如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,所有生产者和消费者都是异步方式运行的,但它们必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经装满产品且尚未被取走的缓冲区中投放产品。
#include
#include
#include
#include
using namespace std;
const int BUFFER_LENGTH = 3;
int buffer[BUFFER_LENGTH];
int front = 0, rear = -1; // 缓冲区的前端和尾端
int size = 0;
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t empty_cond = PTHREAD_COND_INITIALIZER;
pthread_cond_t full_cond = PTHREAD_COND_INITIALIZER;
bool producer_wait = false;
bool consumer_wait = true;
void *producer(void *arg);
void *consumer(void *arg);
int main(int argc, char **argv)
{
pthread_t producer_id[2];
pthread_t consumer_id;
pthread_create(&producer_id[0], NULL, producer, NULL);
pthread_create(&producer_id[1], NULL, producer, NULL);
pthread_create(&consumer_id, NULL, consumer, NULL);
pthread_join(producer_id[0],NULL);
pthread_join(producer_id[1],NULL);
pthread_join(consumer_id,NULL);
sleep(1);
return 0;
}
void *producer(void *arg)
{
int i=0;
while(i++<12)
{
pthread_mutex_lock(&mymutex);
if (size == BUFFER_LENGTH) // 如果缓冲区已满,等待; 否则,添加新产品
{
printf("buffer is full. producer is waiting...\n");
producer_wait = true;
pthread_cond_wait(&full_cond, &mymutex);
producer_wait = false;
}
// 往尾端添加一个产品
rear = (rear + 1) % BUFFER_LENGTH;
buffer[rear] = rand() % BUFFER_LENGTH;
printf("producer[%ld] produces the item %d: %d\n",(long)pthread_self(), rear, buffer[rear]);
++size;
if(consumer_wait)
pthread_cond_signal(&empty_cond);
pthread_mutex_unlock(&mymutex);
}
sleep(2);
return (void *)0;
}
void *consumer(void *arg)
{
int i=0;
while(i++<6)
{
pthread_mutex_lock(&mymutex);
if (size == 0) // 如果缓冲区已空,等待; 否则,消费产品
{
printf("buffer is empty. consumer is waiting...\n");
consumer_wait = true;
pthread_cond_wait(&empty_cond, &mymutex);
consumer_wait = false;
}
// 从前端消费一个产品
printf("consumer[%ld] consumes an item %d: %d\n",(long)pthread_self(), front, buffer[front]);
front = (front + 1) % BUFFER_LENGTH;
--size;
if(producer_wait)
pthread_cond_signal(&full_cond);
pthread_mutex_unlock(&mymutex);
}
return (void *)0;
}
第七题
读者写者问题
这也是一个非常经典的多线程题目,题目大意如下:有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者读时写者也不能写。
(备注:这上面的代码是拷贝的,如果要读写文件的话,还有一个问题就是文件偏移量的问题:线程是共享一个偏移量,读写都是针对一个偏移量的话。所以读写的文件句柄一定要分开。)