并发编程面试题
最近一直在看并发编程的相关知识,所以从网上搜了一些面试题,自己尝试着去解答,来检验最近的学习情况:
线程是一个程序控制流,是cpu调度执行作业的最小单位,线程一般隶属于某个进程。
进程是应用程序,是资源分配的最小单位,比如内存、硬盘,一个进程至少包含一个线程。
继承Thread重写run、实现Runnable重写run、或者创建线程池
最好用Runnable,因为java不支持多继承,如果任务继承了Thread就不能再继承其他父类
6) Thread 类中的start() 和 run() 方法有什么区别?
调用start方法,线程进入就绪状态,调用run就是调用Thread对象的普通方法,不会启动线程
7) Java中Runnable和Callable有什么不同?
runnable没有返回值,而且异常会报出来,callable有返回值,如果有异常会封装在callable中,
可以通过get获取
8) Java中CyclicBarrier 和 CountDownLatch有什么不同?
cyclicBarrier可以重复利用,而且到达后可以异步启动一个线程runnable,countDownLatch不可以重复利用
为了屏蔽jvm与平台硬件的不兼容,所以java有主内存和工作内存,操作一个变量首先要从主内存加载到工作内存,
操作完成后,在刷新到主内存。
volatile保证线程之间的可见性与禁止指令重排序。
写:一个线程修改了变量的值,会被强制刷新到主内存。读:一个线程会强制令工作内存的状态栏失效,然后从主内存
中读取,加载到工作内存。
禁止指令重排序,栅栏屏蔽
线程安全是指多个线程访问一个类,如果访问之后的结果和一个线程访问之后的结果相同,那么可以认为该类是线程安全的。
vector是线程安全的类,因为vector类加了表锁,同一时刻只允许一个线程访问类
竞态条件就是同一个时刻只能有一个线程访问资源
调用interrupt方法
线程终止,抛出异常
主内存、wait/notify、await/signal
16) Java中notify 和 notifyAll有什么区别?
notify会随机唤醒一个阻塞的线程,notifyAll会唤醒所有阻塞的线程
17) 为什么wait, notify 和 notifyAll这些方法不在thread类里面?
因为锁是对象级别的,不是线程级别的
threadLocal可以保证线程安全,通过空间换时间,Thread类上有个threadLocals变量,
threadLocal维护了Entry,entry中维护了threadlocal对象与需要保证线程安全的变量之间的映射关系,也就是说
每个线程都会将这个变量的副本维护到自己的线程对象上,是线程隔离的
futureTask就是对runnable的包装,也实现了runnable和future接口,futureTask的run方法会调用被包装的runnable对象的call
如果成功则将结果包装到outcome字段,失败则将异常包装到outcome上,然后通过report返回,通过get方法可以阻塞获取
20) Java中interrupted 和 isInterruptedd方法的区别?
collections提供的静态方法同步集合会把方法放到synchronized代码块中,锁的粒度比较大
堆是用来放内存对象,为所有线程共享,栈又来放置基本数据和引用地址,是线程私有的
线程池是用来维护线程的一个容器,使用线程池可以不用频繁的创建和销毁线程,提高cpu的利用率,
而且当任务提交给线程池时,可以直接使用线程池维护的线程处理,提高响应速度。
死锁的四个条件:
1:独占资源,互斥条件
2:请求和保持,线程在占有一个资源后,又申请另外的独占资源
3:不可剥夺,线程占有的资源除非自己释放,否则不可被剥夺
4:循环等待,线程申请资源形成了一个环状等待
只有打破其中的一个条件,就不会出现死锁。
通常做法只要保证每个线程申请锁的顺序一致,就不会出现死锁
死锁是因为线程占有一个独占资源,同时又申请其他的资源,形成了循环等待,每个线程都在阻塞。
活锁没有阻塞,但是会因为永远满足不了条件而不断循环执行。
Thread.holdsLock
Thread.getAllStackTraces()
xns
32) Java中synchronized 和 ReentrantLock 有什么不同?
synchronized是内置锁,支持可重入,默认非公平,阻塞不可中断。
ReentrantLock是显示锁,更加灵活,支持可尝试获取锁,可中断获取锁,尝试带超时时间获取锁
33) 有三个线程T1,T2,T3,怎么确保它们按顺序执行?
join
放弃cpu的执行权,重新分配时间片
35) Java中ConcurrentHashMap的并发度是什么?
jdk1.7 16 jdk1.8不在使用片段锁,而是采用表+单向链表+红黑树,将锁的粒度缩小至单向链表上
信号量,可以控制并发线程的数量
如果最大线程大于核心线程,那么会创建新的线程处理任务,如果最大线程等于核心线程,那么
会启用reject策略
38) Java线程池中submit() 和 execute()方法有什么区别?
submit一般提交有返回值的callable,也可以提交runnable任务,提交到submit的任务,会被包装成FutureTask,然后当线程调用start后
会执行FutureTask方法的run,调用call或者run,抛异常会被捕捉,封装到outcome中,没有异常把结果封装到outcome中,然后返回,get可以
阻塞拿到结果。
execute没有包装,start后直接,cpu直接调用worker的run 方法,runWork 调用runnable的run,异常会抛出。
get,调用方法时,会被阻塞。
46)volatile 变量和 atomic 变量有什么不同?
懒汉式:双重检查
饿汉式:私有化构造器、静态方法创建实例
或者使用枚举
这个问题就像是如何强制进行Java垃圾回收,目前还没有觉得方法,虽然你可以使用System.gc()来进行垃圾回收,但是不保证能成功。
在Java里面没有办法强制启动一个线程,它是被线程调度器控制着且Java没有公布相关的API。
fork join框架是线程池的另外一种实现,可以将任务不断拆分成小的单元,然后执行,执行结果在合并,使用了
递归的方式。
53) Java多线程中调用wait() 和 sleep()方法有什么不同?
wait和sleep都会放弃cpu的执行权,进入阻塞,wait的线程必须等待notify唤醒,sleep时间到后会
自动进入就绪状态。wait后放弃锁,sleep不会放弃锁。