多线程面试
synchronized有什么作用,底层是如何实现的呢
【答案解析】
方法或代码块的互斥性来完成实际上的一个原子操作。(方法或代码块在被一个线程调用时,其他线程处于等待状态)
所有的Java对象都有一个与synchronzied关联的监视器对象(monitor),允许线程在该监视器对象上进行加锁和解锁操作。【在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。】
a、静态方法:Java类对应的Class类的对象所关联的监视器对象。
b、实例方法:当前对象实例所关联的监视器对象。
c、代码块:代码块声明中的对象所关联的监视器对象。
【备注】更多可以参照体系里面的文章:
synchronized关键字
violatile关键字有什么作用呢?
【答案解析】
volatile关键词:用来对共享变量的访问进行同步,上一次写入操作的结果对下一次读取操作是肯定可见的。(在写入volatile变量值之后,CPU缓存中的内容会被写回内存;在读取volatile变量时,CPU缓存中的对应内容会被置为失效,重新从主存中进行读取),volatile不使用锁,性能优于synchronized关键词。
用来确保对一个变量的修改被正确地传播到其他线程中。
【注意】
- 基本类型,引用类型的复制引用是原子操作;(即一条指令完成)
- long与double的赋值,引用是可以分割的,非原子操作;
【原因:但是java对long和double的赋值操作是非原子操作!!long和double占用的字节数都是8,也就是64bits。在32位操作系统上对64位的数据的读写要分两步完成,每一步取32位数据。这样对double和long的赋值操作就会有问题:如果有两个线程同时写一个变量内存,一个进程写低32位,而另一个写高32位,这样将导致获取的64位数据是失效的数据。因此需要使用volatile关键字来防止此类现象。volatile本身不保证获取和设置操作的原子性,仅仅保持修改的可见性。但是java的内存模型保证声明为volatile的long和double变量的get和set操作是原子的。】
【在64位JVM中double和long的赋值操作是原子操作。】 - 要在线程间共享long或double的字段时,必须在synchronized中操作,或是声明成volatile
Object类的wait、notify和notifyAll方法有什么作用呢?
【答案解析】
线程之间锁的使用方式,是Java中多线程锁的基础,不过实际项目中一般会使用高级API,如果不行的话会最后使用这个
【备注】更多可以参照体系里面的文章:
Object类的wait、notify和notifyAll方法
java中如何实现一个线程呢?如果需要获取线程的返回结果该如何做呢
【答案解析】
3种实现方式:继承Thread,实习Runnable接口或者实现Callable接口
如果要获取线程的返回结果的话就实现CallAble接口
【备注】更多可以参照体系里面的文章:
基本线程类
线程状态有哪些,如何停止运行一个线程?线程状态转化方法有哪些呢
【答案解析】
线程可以处于下列状态之一::NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING(有超时的等待)、TERMINATED。
线程状态转换图:
【备注】
可以参照体系里面的文章:
基本线程类
如何实现一个线程池呢?
【答案解析】
// 可变大小线程池,按照任务数来分配线程,
ExecutorService e = Executors.newCachedThreadPool();
// 单线程池,相当于FixedThreadPool(1)
ExecutorService e = Executors.newSingleThreadExecutor();
// 固定大小线程池。实际使用第3种会比较多
ExecutorService e = Executors.newFixedThreadPool(3);
// 然后运行
e.execute(new MyRunnableImpl());
线程执行过程中,抛出一个RuntimeException,线程会是什么状态呢
【答案解析】
如果没有try {}catch包裹的话线程会中止
并发包下面必须掌握的相关知识:
【答案解析】
分为以下:
ThreadLocal类
原子类(AtomicInteger、AtomicBoolean……):乐观锁会使用这个
ReentrantLock和ReentrantReadWriteLock加锁和释放锁,实际项目一般使用这个类
容器类:BlockingQueue、ConcurrentHashMap、CopyOnWriteArrayList,很多场景使用容器类可以很轻松解决
可以参照体系里面的文章:
高级多线程控制类
讲讲对多线程的理解
【答案解析】
多线程开发中,最好每个线程是独立的,这样不用加锁,性能与代码安全性都好,比如起了5个线程处理List,可以实现用hash函数把list分成5份,每个线程启动的时候只处理自己对应的那一份就行。
使用容器类,比如BlockQueue阻塞队列或者ConCurrentHashMap,利用生产消费者等设计模型,能够解决绝大多数并发问题。
线程内部的独立变量如果不与外部交互的话,可以使用ThreadLocal类。
多线程开发中应该优先使用高层API(比如信号量Semaphore,回环栅栏CyclicBarrier等,也需要对应的使用场景),如果无法满足,使用java.util.concurrent.atomic(比如AtomicInteger原子操作类)和java.util.concurrent.locks包提供的中层API(一般都是用ReentrantLock的lock(),unlick()方法处理锁,用的主要也是中层API),而synchronized和volatile,以及wait,notify和notifyAll等低层API 应该最后考虑。
请实现一个生产消费者模型
【答案解析】
可以直接使用阻塞队列,也可以使用普通的list,但是这个时候要加上对象Object的wait和notify/notifyAll
public class BlockingQueueTest {
//生产者
public static class Producer implements Runnable{
private final BlockingQueue<Integer> blockingQueue;
private volatile boolean flag;
private Random random;
public Producer(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
flag=false;
random=new Random();
}
public void run() {
while(!flag){
int info=random.nextInt(100);
try {
blockingQueue.put(info);
System.out.println(Thread.currentThread().getName()+" produce "+info);
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void shutDown(){
flag=true;
}
}
//消费者
public static class Consumer implements Runnable{
private final BlockingQueue<Integer> blockingQueue;
private volatile boolean flag;
public Consumer(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
}
public void run() {
while(!flag){
int info;
try {
info = blockingQueue.take();
System.out.println(Thread.currentThread().getName()+" consumer "+info);
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void shutDown(){
flag=true;
}
}
public static void main(String[] args){
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>(10);
Producer producer=new Producer(blockingQueue);
Consumer consumer=new Consumer(blockingQueue);
//创建5个生产者,5个消费者
for(int i=0;i<10;i++){
if(i<5){
new Thread(producer,"producer"+i).start();
}else{
new Thread(consumer,"consumer"+(i-5)).start();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
producer.shutDown();
consumer.shutDown();
}
}
请实现一个公平锁
公平锁:
公平和非公平锁的队列都基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。公平锁则在于每次都是依次从队首取值。
非公平锁:
在等待锁的过程中, 如果有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的。
基于ReentrantLock可以实现公平锁与非公平锁,构造函数根据数据参数内容调用FairSync或者NonFairSync,就可以实现公平锁或者非公平锁。
private static final ReentrantLock lock = new ReentrantLock(); //非公平锁
private static final ReentrantLock fairlock = new ReentrantLock(true); //公平锁
公平锁FairSync的核心是锁内部有一个双向链表,每次获取锁都是依次从链表队首取值。
可以参照完整范例加深理解:
public class ReentrantLockTest {
private static final ReentrantLock lock = new ReentrantLock();
private static final ReentrantLock fairlock = new ReentrantLock(true);
private int n;
public static void main(String[] args) {
ReentrantLockTest rlt = new ReentrantLockTest();
for (int i = 0; i < 100; i++) {
Thread nonT = new Thread(new NonFairTestThread(rlt));
nonT.setName("nonFair[" + (i + 1) + "]");
nonT.start();
Thread fairT = new Thread(new FairTestThread(rlt));
fairT.setName("fair[" + (i + 1) + "]");
fairT.start();
}
}
// 非公平锁
static class NonFairTestThread implements Runnable {
private ReentrantLockTest rlt;
public NonFairTestThread(ReentrantLockTest rlt) {
this.rlt = rlt;
}
public void run() {
lock.lock();
try {
rlt.setNum(rlt.getNum() + 1);
System.out.println(Thread.currentThread().getName()
+ " nonfairlock***************" + rlt.getNum());
} finally {
lock.unlock();
}
}
}
// 公平锁
static class FairTestThread implements Runnable {
private ReentrantLockTest rlt;
public FairTestThread(ReentrantLockTest rlt) {
this.rlt = rlt;
}
public void run() {
fairlock.lock();
try {
rlt.setNum(rlt.getNum() + 1);
System.out.println(Thread.currentThread().getName()
+ " fairlock=======" + rlt.getNum() + " "
+ fairlock.getHoldCount() + " queuelength="
+ fairlock.getQueueLength());
} finally {
fairlock.unlock();
}
}
}
public void setNum(int n) {
this.n = n;
}
public int getNum() {
return n;
}
}
【备注】ReentrantLock的公平锁与非公平锁的源码解析:
上面的lock()方法最后都会调用acquire(int arg) 这个方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && //这个方法获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //这个方法是阻塞
selfInterrupt(); //这个就是interrupt()阻断方法
}
然后:公平锁FairSync:
公平锁的实现机理在于每次有线程来抢占锁的时候,都会检查一遍有没有等待队列,如果有, 当前线程会执行如下步骤:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
///如果当前线程是等待队列的第一个或者等待队列为空,则通过cas指令设置state为1,当前线程获得锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //如果当前线程本身就持有锁,那么叠加状态值,持续获得锁
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
其中hasQueuedPredecessors是用于检查是否有等待队列的。
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
非公平锁NonfairSync
非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式:
非公平锁的TryAcquire方法源码如下
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
然后就是:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//如果当前没有线程占有锁,当前线程直接通过cas指令占有锁,管他等待队列,就算自己排在队尾也是这样
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}