java并发面试题

java并发面试题

一、基础概念

1.java内存模型

在这里插入图片描述
在这里插入图片描述

2.as-if-serial

不管怎么重排序,单线程下的执行结果不能被改变。
编译器、runtime和处理器都必须遵守as-if-serial语义。

3.happens-before原则

详解

二、CAS相关

1.CAS怎么实现线程安全

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.CAS缺点

ABA问题

在这里插入图片描述
在这里插入图片描述

循环时间长开销大的问题

在这里插入图片描述

只能保证一个共享变量的原子操作

在这里插入图片描述

3.解决ABA问题

在这里插入图片描述

update table set value = newValue ,vision = vision + 1 where value = #{oldValue} and vision = #{vision} 
// 判断原来的值和版本号是否匹配,中间有别的线程修改,值可能相等,但是版本号100%不一样

在这里插入图片描述

三、synchronized相关

1.synchronized概述

在这里插入图片描述

2.synchronized底层原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

monitor对象

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.synchronized和ReentrantLock区别

在这里插入图片描述
在这里插入图片描述

4.synchronized和volatile的区别

在这里插入图片描述

5.锁的四种状态及升级过程

在这里插入图片描述
在这里插入图片描述

jdk1.6之后锁的优化

在这里插入图片描述
在这里插入图片描述

四、volatile相关

1.实现双重检测的单例模式

public class SingleInstance {
    private SingleInstance() {}
    private static volatile SingleInstance INSTANCE;
    public static SingleInstance getInstance() {
        if (INSTANCE == null) {
            synchronized (SingleInstance.class) {
                if (INSTANCE == null) {
                    INSTANCE = new SingleInstance();
                }
            }
        }
        return INSTANCE;
    }
}

DDL单例为什么要加volatile

在这里插入图片描述
在这里插入图片描述

2.解决内存可见性问题

详解

3.禁止指令重排

在这里插入图片描述

五、ReentrantLock相关

1.lock和trylock方法加锁的区别

在这里插入图片描述

2.公平锁和非公平锁的实现

ReentrantLock类的结构

在这里插入图片描述
重入锁的核心功能委托给内部类Sync实现,并且根据是否是公平锁有FairSync和NonfairSync两种实现。这是一种典型的策略模式。

重入锁实现

实现重入锁的方法很简单,就是基于一个状态变量state。这个变量保存在AbstractQueuedSynchronizer对象中。

private volatile int state;

当这个state==0时,表示锁是空闲的,大于零表示锁已经被占用, 它的数值表示当前线程重复占用这个锁的次数。因此,lock()的最简单的实现是:

 final void lock() {
 // compareAndSetState就是对state进行CAS操作,如果修改成功就占用锁
 if (compareAndSetState(0, 1))
     setExclusiveOwnerThread(Thread.currentThread());
 else
 //如果修改不成功,说明别的线程已经使用了这个锁,那么就可能需要等待
     acquire(1);
}

下面是acquire() 的实现:

 public final void acquire(int arg) {
 //tryAcquire() 再次尝试获取锁,
 //如果发现锁就是当前线程占用的,则更新state,表示重复占用的次数,
 //同时宣布获得所成功,这正是重入的关键所在
 if (!tryAcquire(arg) &&
     // 如果获取失败,那么就在这里入队等待
     acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
     //如果在等待过程中 被中断了,那么重新把中断标志位设置上
     selfInterrupt();
}
公平锁和非公平锁区别
//非公平锁 
 final void lock() {
     //上来不管三七二十一,直接抢了再说
     if (compareAndSetState(0, 1))
         setExclusiveOwnerThread(Thread.currentThread());
     else
         //抢不到,就进队列慢慢等着
         acquire(1);
 }

 //公平锁
 final void lock() {
     //直接进队列等着
     acquire(1);
 }

非公平锁如果第一次争抢失败,后面的处理和公平锁是一样的,都是进入等待队列慢慢等。

3.Condition

概述

Condition接口可以理解为重入锁的伴生对象。它提供了在重入锁的基础上,进行等待和通知的机制。可以使用 newCondition()方法生成一个Condition对象。

private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
应用场景

在ArrayBlockingQueue阻塞队列中,就维护一个Condition对象

lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();

这个notEmpty 就是一个Condition对象。它用来通知其他线程,ArrayBlockingQueue是不是空着的。当我们需要拿出一个元素时:

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            // 如果队列长度为0,那么就在notEmpty condition上等待了,一直等到有元素进来为止
            // 注意,await()方法,一定是要先获得condition伴生的那个lock,才能用的哦
            notEmpty.await();
        //一旦有人通知我队列里有东西了,我就弹出一个返回
        return dequeue();
    } finally {
        lock.unlock();
    }
}

当有元素入队时:


   public boolean offer(E e) {
     checkNotNull(e);
     final ReentrantLock lock = this.lock;
     //先拿到锁,拿到锁才能操作对应的Condition对象
     lock.lock();
     try {
         if (count == items.length)
             return false;
         else {
             //入队了, 在这个函数里,就会进行notEmpty的通知,通知相关线程,有数据准备好了
             enqueue(e);
             return true;
         }
     } finally {
         //释放锁了,等着的那个线程,现在可以去弹出一个元素试试了
         lock.unlock();
     }
 }

 private void enqueue(E x) {
     final Object[] items = this.items;
     items[putIndex] = x;
     if (++putIndex == items.length)
         putIndex = 0;
     count++;
     //元素已经放好了,通知那个等着拿东西的人吧
     notEmpty.signal();
 }

在这里插入图片描述

4.LockSupport类

LockSupport可以理解为一个工具类。它的作用很简单,就是挂起和继续执行线程。它的常用的API如下:

public static void park() : 如果没有可用许可,则挂起当前线程
public static void unpark(Thread thread):给thread一个可用的许可,让它得以继续执行

5.AbstractQueuedSynchronizer内部数据结构

在AbstractQueuedSynchronizer内部,有一个队列,我们把它叫做同步等待队列。它的作用是保存等待在这个锁上的线程(由于lock()操作引起的等待)。此外,为了维护等待在条件变量上的等待线程,AbstractQueuedSynchronizer又需要再维护一个条件变量等待队列,也就是那些由Condition.await()引起阻塞的线程。

在这里插入图片描述
在这里插入图片描述
可以看到,无论是同步等待队列,还是条件变量等待队列,都使用同一个Node类作为链表的节点。
在这里插入图片描述

6.AQS对资源的共享方式

在这里插入图片描述
AQS底层使用了模板方法模式

六、锁分类

1.互斥锁和自旋锁

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.乐观锁和悲观锁你的理解,使用场景

乐观锁
概念

先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。

使用场景

在线文档
我们都知道在线文档可以同时多人编辑的,如果使用了悲观锁,那么只要有一个用户正在编辑文档,此时其他用户就无法打开相同的文档了,这用户体验当然不好了。

SVN和Git
先让用户编辑代码,然后提交的时候,通过版本号来判断是否产生了冲突,发生了冲突的地方,需要我们自己修改后,再重新提交。

悲观锁
概念

认为多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁。

使用场景

互斥锁、自旋锁、读写锁,都是属于悲观锁。

3.读锁和写锁

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.可重入锁

在这里插入图片描述

七、线程

1.什么是线程安全

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其它的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。

2.如何创建线程

1)继承Thread类

优势:编程比较简单,可以直接使用Thread类中的方法。

劣势:可扩展性较差,不能再继承其他的类。

2)实现Runnable接口

优势:扩展性强,实现该接口的同时还可以继承其他的类。

劣势:编程相对复杂,不能直接使用Thread类中的方法。

3)实现Callable接口

Callable 执行的任务有返回值,而 Runnable 执行的任务没有返回值。可以通过FutureTask中的get方法获取返回值。

Callable(重写)的方法是 call 方法,而 Runnable(重写)的方法是 run 方法。

call 方法可以抛出异常,而 Runnable 方法不能抛出异常。

4)使用线程池

在这里插入图片描述

3.线程间的通信方式

volatile关键字方式

利用volatile保证可见性,使得其他线程感受到共享变量的变化

等待/通知机制

基于Object类中的wait和notify方法

join方式

当在一个线程调用另一个线程的join方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行。

threadLocal方式

4.wait和notify的底层实现

应用层面

在这里插入图片描述

jvm层面

前置工作

1)进入wait/notify方法之前,要获取synchronized锁

2)synchronized生成的字节码指令有monitorenter和 monitorexit,执行monitorenter指令可以获取对象的monitor

3)线程执行lock.wait()方法时,必须持有该lock对象的monitor,如果wait方法在synchronized代码中执行,该线程很显然已经持有了monitor。

4)在HotSpot虚拟机中,monitor采用ObjectMonitor实现。

在这里插入图片描述
5)ObjectMonitor对象中有两个队列:_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表;_owner指向获得ObjectMonitor对象的线程。

在这里插入图片描述
_WaitSet :处于wait状态的线程,会被加入到wait set;
_EntryList:处于等待锁block状态的线程,会被加入到entry set;

6)ObjectWaiter对象是双向链表结构,保存了_thread(当前线程)以及当前的状态TState等数据, 每个等待锁的线程都会被封装成ObjectWaiter对象。

在这里插入图片描述
wait方法实现
lock.wait()方法最终通过ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);实现:

1)将当前线程封装成ObjectWaiter对象node;

在这里插入图片描述
2)通过ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中;

在这里插入图片描述
3)通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象。

在这里插入图片描述
4)最终底层的park方法会挂起线程;

notify方法实现
lock.notify()方法最终通过ObjectMonitor的void notify(TRAPS)实现:

1)如果当前_WaitSet为空,即没有正在等待的线程,则直接返回;

2)通过ObjectMonitor::DequeueWaiter方法,获取_WaitSet列表中的第一个ObjectWaiter节点,实现也很简单。

这里需要注意的是,在jdk的notify方法注释是随机唤醒一个线程,其实是第一个ObjectWaiter节点

在这里插入图片描述
3)根据不同的策略,将取出来的ObjectWaiter节点,加入到_EntryList或则通过

5.线程切换方式

在这里插入图片描述

6.程序一般开多少线程

在这里插入图片描述

7.notify和notifyAll区别

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.wait和sleep的区别

在这里插入图片描述

八、线程池

1.线程池优点

降低资源消耗

重用存在的线程,减少对象创建销毁的开销。

提高响应速度

可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

提高线程的可管理性

线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

附加功能

提供定时执行、定期执行、单线程、并发数控制等功能。

2.线程池有哪几种

newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

3.线程池参数

corePoolSize: 线程池核心线程数最大值
maximumPoolSize: 线程池最大线程数大小
keepAliveTime: 线程池中非核心线程空闲的存活时间大小
unit: 线程空闲存活时间单位
workQueue: 存放任务的阻塞队列
threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。
handler: 线城池的拒绝策略事件,主要有四种类型。

4.线程池的拒绝策略

在这里插入图片描述

5.执行execute()方法和submit()方法的区别

在这里插入图片描述

6.线程池工作原理

在这里插入图片描述

九、并发常见类

1.ConCurrentHashMap

出现背景

1)HashMap线程不安全,在1.7中采用头插法会造成死循环,在1.8中改为尾插法,会造成元素覆盖。

2)HashTable集合类和Collections下的SynchronizedMap类是线程安全的,但是会锁住整个表,效率低下。

jdk1.7和1.8中采用的技术

1)jdk1.7中ConcurrentHashMap采用锁分段技术,每个部分是一个Segment。

2)jdk1.8中采用Synchronized + CAS,把锁的粒度进一步降低

jdk1.7的底层原理
存储结构

在这里插入图片描述
1)采用链表加数组的数据结构,把原来的整个table划分为n个 Segment 。每个 Segment 里边是由 HashEntry 组成的数组,每个 HashEntry之间又可以形成链表。

2)当对某个 Segment 加锁时,并不会影响到其他 Segment 的读写。

put方法的流程

1)通过哈希算法计算出当前 key 的 hash 值

2)通过这个hash值找到它所对应的Segment数组的下标

3)再通过hash值计算出它在对应Segment的HashEntry数组 的下标

4)找到合适的位置插入元素

size方法底层实现

首先采用乐观的方式,认为统计 size 的过程中,并没有发生 put, remove 等会改变 Segment 结构的操作。遍历统计count和modcount的个数,其中count指的是每个Segment元素的个数,modcount指的是每次 table 结构修改时,如put,remove等,此变量都会自增。如果发生了修改,则需要重试,重试两次都不成功,则需要把所有segment加锁之后再计算。

jdk1.8底层原理
存储结构

数组+链表+红黑树,不再有Segment的概念,而是给数组中的每一个头节点(桶)都加锁,使用的是Synchronized 锁。在jdk1.6之后,Synchronized引入了锁升级的概念。

put方法

若当前桶为空,则通过 CAS 原子操作,把新节点插入到此位置

2.AtomicInteger类的原理

在这里插入图片描述
在这里插入图片描述

3.CountDownLatch

在这里插入图片描述

4.CyclicBarrier

一组线程会互相等待,直到所有线程都到达一个同步点。这个就非常有意思了,就像一群人被困到了一个栅栏前面,只有等最后一个人到达之后,他们才可以合力把栅栏(屏障)突破。

5.Semaphore

在这里插入图片描述

十、ThreadLocal

1.作用

在这里插入图片描述

2.应用场景

在这里插入图片描述

3.底层原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

十一、并发编程题

1.手写死锁

package com.shenhao;

public class Demo {
    public static void main(String[] args) {
        Object objA = new Object();
        Object objB = new Object();

        new Thread(() -> {
            while(true){
                synchronized (objA){
                    //线程一
                    synchronized (objB) {
                        System.out.println("小A正在走路");
                    }
                }
            }
        }).start();

        new Thread(() -> {
            while(true){
                synchronized (objB){
                    //线程二
                    synchronized (objA) {
                        System.out.println("小B正在走路");
                    }
                }
            }
        }).start();
    }
}

2.手写生产者消费者

生产者

package com.shenhao.threaddemo16;

import java.util.concurrent.ArrayBlockingQueue;

public class Producer extends Thread{
    private ArrayBlockingQueue<String> abq;

    public Producer(ArrayBlockingQueue<String> abq){
        this.abq = abq;
    }

    @Override
    public void run() {
        while(true){
            try {
                abq.put("用品");
                System.out.println("生产者生产一个用品");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者

package com.shenhao.threaddemo16;

import java.util.concurrent.ArrayBlockingQueue;

public class Consumer extends Thread{
    private ArrayBlockingQueue<String> abq;

    public Consumer(ArrayBlockingQueue<String> abq){
        this.abq = abq;
    }

    @Override
    public void run() {
        while(true){
            String s = null;
            try {
                s = abq.take();
                System.out.println("消费者消费" + s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

主程序调用

package com.shenhao.threaddemo16;

import java.util.concurrent.ArrayBlockingQueue;

public class Demo {

    public static void main(String[] args) {
        //阻塞队列,容量为1
        ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(1);

        Producer p = new Producer(abq);
        Consumer c = new Consumer(abq);

        p.start();
        c.start();
    }
}

3.手写阻塞队列

阻塞队列

package com.shenhao.threaddemo14;

public class Desk {
    //状态
    private boolean flag;

    //商品数量
    private int count = 10;

    //锁对象
    private final Object lock = new Object();

    public Desk() {
    }

    public Desk(boolean flag, int count) {
        this.flag = flag;
        this.count = count;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public Object getLock() {
        return lock;
    }

    @Override
    public String toString() {
        return "Desk{" +
                "flag=" + flag +
                ", count=" + count +
                ", lock=" + lock +
                '}';
    }
}

生产者

package com.shenhao.threaddemo14;

public class Producer extends Thread{
    private Desk desk;

    public Producer(Desk desk) {
        this.desk = desk;
    }

    @Override
    public void run() {
        while(true){
            synchronized (desk.getLock()){
                if(desk.getCount() == 0){
                    break;
                }else{
                    if(!desk.isFlag()){
                        System.out.println("生产者正在生产" + desk.getCount());
                        desk.setFlag(true);
                        desk.getLock().notify();
                    }else{
                        try {
                            desk.getLock().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

消费者

package com.shenhao.threaddemo14;

public class Consumer extends Thread{
    private Desk desk;

    public Consumer(Desk desk) {
        this.desk = desk;
    }

    @Override
    public void run() {
        while(true){
            synchronized (desk.getLock()){
                if(desk.getCount() == 0){
                    break;
                }else{
                    if(desk.isFlag()){
                        System.out.println("消费者正在消费" + desk.getCount());
                        desk.setFlag(false);
                        desk.getLock().notify();
                        desk.setCount(desk.getCount() - 1);
                    }else{
                        try {
                            desk.getLock().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }


            }
        }
    }
}

主程序调用

package com.shenhao.threaddemo14;

public class Demo {
    public static void main(String[] args) {
        Desk desk = new Desk();

        Consumer consumer = new Consumer(desk);
        Producer producer = new Producer(desk);

        consumer.start();
        producer.start();
    }
}

4.实现多个线程顺序打印abc

public class JoinDemo{
    //三个线程顺序执行
    public static void main(String[] args) {
        Work t1=new Work(null,"A");
        Work t2=new Work(t1,"B");
        Work t3 =new Work(t2,"C");
        t1.start();
        t2.start();
        t3.start();
    }
     static class Work extends Thread{
        private Thread Thread;
        private String threadName;
         public Work(Thread Thread,String threadName) {
             this.Thread = Thread;
             this.threadName=threadName;
         }

         @Override
         public void run() {
             if (Thread != null) {
                 try {
                     Thread.join();

                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             System.out.println(threadName);
         }
     }

}


5.两个线程轮流打印数字,一直到100

package com.shenhao;


public class Test{
    static int num=0;
    public static void main(String[] args) {
        Object o = new Object(); //要锁住的资源类
        Runnable runnable =
                new Runnable() {
                    @Override
                    public void run() {
                        while (num < 100) {
                            synchronized (o) {
                                o.notify();
                                num++;
                                System.out.println(Thread.currentThread().getName() + ":" + num);
                                try {
                                    //避免b线程最后不能被唤醒一直等待导致线程不结束
                                    if(num<100){
                                        o.wait();
                                    }else {
                                        o.notify();
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                        System.out.println(Thread.currentThread().getName()+"结束");
                    }
                };

        new Thread(runnable,"a").start();
        new Thread(runnable,"b").start();
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值