Java并发编程面试题

1.实现线程的几种方式

  1. 继承Thread类
public class SubClassTest {
    public static void main(String[] args) {
        new Thread(new MyThread2()).start();
    }
}
class MyThread2 extends Thread{
    @Override
    public void run() {
        System.out.println("Here is in subClass!");
    }
}
//输出
Here is in subClass!
  1. 继承Runnable接口
public class RunnableTest {
    public static void main(String[] args) {
        new Thread(new MyThread3()).start();
    }
}
class MyThread3 implements Runnable{
    @Override
    public void run() {
        System.out.println("Here is Runnable!");
    }
}
//输出
Here is Runnable!
  1. 继承Callable接口,要配合FutureTask接受结果
    这里通过开启一个子线程求平方
public class CallableTest{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread(5);
        FutureTask<Integer> f = new FutureTask<>(myThread);
        new Thread(f).start();
        System.out.println(f.get());
        Thread t = new Thread();
        t.start();
    }
}
 class MyThread implements Callable<Integer> {
    private int x;
    public MyThread(int x){
        this.x = x;
    }
    @Override
    public Integer call() throws Exception {
        System.out.println("Here is call !");
        x = x*x;
        return x;
    }
}
//输出
Here is call !
25

4.2种类线程池

  1. newCachedThreadPool
    可以添加任意多的线程;超时1分钟后可以回收;一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}
  1. newFixedThreadPool
    典型且优秀的线程池;但线程池没有任务时不会被销毁
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
  1. newSingleThreadExecutor
    相比于手动开启一个线程,它多了任务提交失败的机制。如果worker异常,会有另外一个worker代替。保证所有的任务按照指定顺序执行。
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}
  1. newScheduleThreadPool
    可以再延时一段时间后执行任务,或定期执行任务。
public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);
 }

3.Synchronized原理以及优化

1.重量级锁
每个对象锁对应一个monitor对象,该monitor对象是os级别的,维护一个owner,一个EntryList,一个waitset。
初始时owner为null,当线程A进入同步代码快,则成为owner,markword的后两位改成10,前面的位改成指向monitor的指针。如果其他线程也进入,则会进入EntryList等待
2.轻量级锁
应用场景:
一个对象锁中多个线程的访问是错开的,这样就没必要申请os级别的重量级锁。轻量级锁的使用是透明的,也就是默认使用。
方法:
当一个线程进入同步代码块,栈帧中生成Lock Record对象,通过cas将对象的mark word和Lock Record中的字段交换,Lock Record对象还存放了指向对象的指针,交换成功则加了轻量级锁,标志位00
cas失败:
原因一:发生了重入,那么继续生成一个Lock Record对象,但该对象的Lock Record为null。原因二:其他线程拥有了该对象的轻量级锁,进入锁膨胀阶段。
3.锁膨胀
为对象申请重量级锁,并将对象头中的mark word 改成指向重量级锁的指针和10,申请的monitor对象的owner为原来的线程,当前线程进入EntryList等待。
4.偏向锁
使用原因:
轻量级锁每次在发生重入时仍需要通过cas交换mark word,而偏向锁只有在第一次进入同步代码块时通过cas将线程id设置到对象头,以后如果发现该对象mark word包含有当前线程id,就不需要判断。
特点:

  • 默认延时开启
  • 如果使用了偏向锁,在离开synchronized代码块之后后三位仍然是101
  • 当线程1使用完对象锁A之后,线程2也使用了该锁,那么在synchronized代码块内部加的是轻量级锁。
  • 批量重定向和批量撤销
    当有三个线程t1,t2,t3,t1先执行,然后唤醒t2,t2唤醒t3,有40次循环创建40个对象

t1中,所有对象都是偏向t1的

加锁前:000...101

加锁中:threadID_t1+101

加锁后:threadID_t1+101

t2中,前20个对象(其他线程调用,取消偏向状态,阈值超过20重偏向)

加锁前:threadID_t1+101

加锁中:lockrecord+00

加锁后:000...001

后19个对象

加锁前:threadID_t1+101

加锁中:threadID_t2+101

加锁后:threadID_t2+101

t3中,前20个对象

加锁前:000...001

加锁中:lockrecord+00

加锁后:000...001

后19个对象

加锁前:threadID_t2+101

加锁中:lockrecord+00

加锁后:000...001

当new第41个对象时,默认为不可偏向

  1. 锁消除

  2. 自旋优化:
    当需要加重量级锁时,他重新多次尝试能否获得锁,如果获得了就不需要加重量级锁。重试的次数和CPU有关,比如如果刚才重试成功了就会多试几次。

4.Synchronized和Lock

  1. Synchronized基于JVM来实现的,ReentrantLock基于JDK来实现的
  2. 由于对Synchronized做了优化,因此性能上差不多
  3. ReentrantLock可以被打断,即正在等待的线程取消了等待,对应于lockInterruptibly()
  4. Synchronized只有非公平,而ReentrantLock默认非公平,也可以支持公平。
  5. wait/notify上,一个RenntrantLock对象有newCondition(),可以使用多个ConditionObject

5.CAS

简介:CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B,是乐观锁的实现方式。
应用:AtomicInteger的add方法就使用了unsafe类的compareAndSwapInt方法来实现

private AtomicInteger cnt = new AtomicInteger();
public void add() {
    cnt.incrementAndGet();
}
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

6.volatile

可见性:
Java内存模型中,每个线程有自己的工作内存,还有主内存。volatile类型的变量,如果发生改变,那么该线程会给总线发信号,其他线程都在监听总线,如果监听到该信号,会将自己的缓存中对应的volatile变量置为无效,需要重新从主内存中读取新值。
有序性:
加内存屏障,即特定的指令,保证排序的一些规则,有4种,对于volatile类型变量的读会在后面添加LoadStore和LoadLoad内存屏障,保证了后面对普通变量的读和写不会和该volatile变量的读重排序。

7.final

1.写final域重排序规则
在对象引用被其他线程可见之前,对象的final域已经初始化,实现方式是在写操作后加StoreStore屏障。
在下图中,非final域就没有被正确初始化,而final域正常

public class FinalExample {
    int i;                            //普通变量
    final int j;                      //final变量
    static FinalExample obj;

    public FinalExample () {     //构造函数
        i = 1;                        //写普通域
        j = 2;                        //写final域
    }

    public static void writer () {    //写线程A执行
        obj = new FinalExample ();
    }

    public static void reader () {       //读线程B执行
        FinalExample object = obj;       //读对象引用
        int a = object.i;                //读普通域
        int b = object.j;                //读final域
    }
}  
写final域有可能出错的情况

2.读final域的读规则
保证先读包含final域对象的引用,再读final域。而由于发生了重排序普通域可能会先被读,然后对象引用再被读。


读final域的规则

8.happens-before

不是指代码的位置,而是指代码的执行顺序。

  1. 一个线程内部前面的和后面的
  2. 线程的start操作和start后的
  3. volatile读和volatile写
  4. join中的操作和后面的
  5. 传递性
  6. ........

9.单例

  1. 为什么用volatile
    singleton = new Singleton()分为三步走
  • 分配内存
  • 构造函数初始化
  • 赋值给引用
    如果没有volatile,后两步发生指令重排序,进入同步代码块的线程还没有初始化完成,另外一个线程判断不为null,则拿到的就是一个没有被正确初始化的对象。
  1. 为什么两次判断
    开始后可能有两个线程同时执行,一个进入EntryList,另一个进入同步代码块
public class Singleton {
    private Singleton(){}
    private static volatile Singleton singleton;
    public Singleton getSingleton(){
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null)
                    singleton = new Singleton();
            }
        }
        return singleton;
    }
}

10. AQS和ReentarantLock

ReentrantLock上锁流程:

  1. 先调用lock()
  2. 先cas将state由0改成1一些看能不能成功,不能就acquire
  3. acquire流程
public final void acquire(int arg) {
// ㈡ tryAcquire
if (!tryAcquire(arg) &&
// 当 tryAcquire 返回为 false 时, 先调用 addWaiter, 接着acquireQueued
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
selfInterrupt();
  }
}
  1. tryAcquire在ReentrantLock中有公平锁和非公平锁两种实现。
    对于公平锁:
    先获取state,如果是0,那么尝试通过cas将0改为1,否则判断是否重入,如果这两种情况满足,则返回true,否则返回false
    对于非公平锁:
    先获取state,如果是0,那么队列中没有其他线程等待且尝试通过cas将0改为1,否则判断是否重入,如果这两种情况满足,则返回true,否则返回false
  2. addWaiter和enq逻辑
    先将当前线程封装到Node里面
    addWaiter是当tail != null时加入队列尾部。enq是开始时head = tail =null的设置
  3. acquiredQueue
    获取尾部先加入节点的前驱节点,如果前驱是head则可以调用tryAcquire再尝试获取锁。(如果成功获取到了,设置当前节点为head并返回)
    没有获取到就会走下一步逻辑
  4. shouldParkAfterFailedAcquire
    这里判断前驱的waitStatus是否为-1,如果不是判断是否<=0,如果是则设置为-1。之后重新进入死循环再执行一遍
  5. parkAndCheckInterrupt
    LockSupport.park阻塞当前线程
    解锁流程:
  6. 调用unlock(),再调用release
  7. 如下图
if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;

3.调用tryRelease判断state - 1后是否为0,如果是设置独占线程为null,并返回true,否则返回只将state减一,返回false。
4.如果h != null && h.waitStatus != 0 就unpark
5.unpark
由于再队列中的线程可能取消了等待,因此从队列最后向前找,如果某个Node的waitStatus = -1,那么调用LockSupport.unpark唤醒它。

11.countdownlatch semaphore cyclicbarrier

  1. CountDownLatch
    应用场景:
    一个线程等待多个线程执行结束
  • 参数中传递的值就是state
  • countDown方法中对应释放共享锁,即将state - 1,Node节点出队。
  • 而await方法对应获取锁,如果state = 0,则执行结束,否则进行入队操作。
  1. Semaphore
    允许多个线程同时访问。
  2. CyclicBarrier
  3. CountDownLatch 和CyclicBarrier的区别
  • CyclicBarrier可以使用多次
  • 对于 CountDownLatch 来说,重点是“一个线程(多个线程)等待”,而其他的 N 个线程在完成“某件事情”之后,可以终止,也可以等待。而对于 CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。

12.await和object.wait的区别

Condition能够支持不响应中断,而通过使用Object方式不支持

Condition能够支持多个等待队列(new 多个Condition对象),而Object方式只能支持一个

Condition能够支持超时时间的设置,而Object不支持
针对Object的wait方法

void await() throws InterruptedException//当前线程进入等待状态,如果在等待状态中被中断会抛出被中断异常long awaitNanos(long nanosTimeout)//当前线程进入等待状态直到被通知,中断或者超时boolean await(long time, TimeUnit unit)throws InterruptedException//同第二种,支持自定义时间单位boolean awaitUntil(Date deadline) throws InterruptedException//当前线程进入等待状态直到被通知,中断或者到了某个时间
针对Object的notify/notifyAll方法

void signal()//唤醒一个等待在condition上的线程,将该线程从等待队列中转移到同步队列中,如果在同步队列中能够竞争到Lock则可以从等待方法中返回。void signalAll()//与1的区别在于能够唤醒所有等待在condition上的线程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容