大厂面试题第二季


一、请谈谈你对volatile的理解

1.volatile是Java虚拟机提供的轻量级的同步机制

※volatile三大特性※

  • 1. 保证可见性
  • 2. 不保证原子性:number++在多线程下是非线程安全的,如何不加synchronized解决?
  • 3. 禁止指令重排

2. JMM你谈谈




JMM三大特性(线程安全性获得保证)

  • 1. 可见性
  • 2. 原子性
  • 3. 有序性

重排1
重排2
禁止指令重排小总结








验证volatile的可见性

class MyData{
    int number = 0;
    public void addT060(){
        this.number = 60;
    }
}

/**
 * 验证volatile的可见性
 * 1.1 假如 int number = 0; number变量之前根本没有添加volatile关键字修饰,没有可见性
 */
public class VolatileDemo {
    public static void main(String[] args) {
        MyData myData = new MyData();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t come in");
            //暂停一会儿线程
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            myData.addT060();
            System.out.println(Thread.currentThread().getName()+"\t update number value:"+myData.number);
        },"AA").start();

        //第二个线程就是我们的main线程
        while(myData.number==0){
            //main线程就一直在这里等待循环,直到number值不再等于零
        }
        System.out.println(Thread.currentThread().getName()+"\t mission is over,main get number value:"+myData.number);
    }
}


给加了volatile关键字之后
volatile可以保证可见性,及时通知其它线程,主物理内存的值已经被修改


验证volatile不保证原子性与解决办法

class MyData{
    volatile int number = 0;
    public void addT060(){
        this.number = 60;
    }

    //请注意,此时number前面加了volatile关键字修饰的,volatile不保证原子性
    public  void addPlusPlus(){
        number++;
    }

    AtomicInteger atomicInteger = new AtomicInteger();
    public void addMyAtomic(){
        atomicInteger.getAndIncrement();
    }
}

/**
 * 1.验证volatile的可见性
 *   1.1 假如 int number = 0; number变量之前根本没有添加volatile关键字修饰,没有可见性
 *   1.2 添加了volatile,可以解决可见性问题。
 * 2.验证volatile不保证原子性
 *   2.1 原子性指的是什么意思?
 *       不可分割,完整性,也即某个线程正在做某个业务时,中间不可以被加塞或被分割。需要整体完整。
 *       要么同时成功,要么同时失败
 *   2.2 volatile不保证原子性案列演示
 *
 *   2.3 why
 *
 *   2.4 如何解决原子性?
 *      加synchronized  太消耗性能
 *      使用我们juc下AtomicInteger
 *
 */
public class VolatileDemo {
    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 1; i <=20 ; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    myData.addPlusPlus();
                    myData.addMyAtomic();
                }
            },String.valueOf(i)).start();
        }
        //需要等待上面20个线程全部都计算完成以后,再用main线程取得最终的结果值看是多少?
        while(Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"\t int type,finally number value:"+myData.number);
        System.out.println(Thread.currentThread().getName()+"\t AtomicInteger type,finally number value:"+myData.atomicInteger);
    }
}



3.你在哪些地方用过volatile?


3.1单例模式DCL代码

public class SingletonDemo {
    private static SingletonDemo instance = null;
    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo");
    }
    //DCL (Double Check Lock双端检锁机制)
    public static SingletonDemo getInstance(){
        if ( instance == null){
            synchronized (SingletonDemo.class){
               if (instance == null){
                   instance = new SingletonDemo();
               }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 1; i <=10; i++) {
            new Thread(()->{
                SingletonDemo.getInstance();
            },String.valueOf(i)).start();
        }
    }
}

3.2单例模式volatile分析


public class SingletonDemo {
    private static volatile SingletonDemo instance = null;
    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo");
    }
    //DCL (Double Check Lock双端检锁机制)
    public static SingletonDemo getInstance(){
        if ( instance == null){
            synchronized (SingletonDemo.class){
               if (instance == null){
                   instance = new SingletonDemo();
               }
            }
        }
        return instance;
    }
}

二 、CAS你知道吗?


1.比较并交换

如果线程的期望值与主物理内存的真实值一样,就修改为我的更新值,本次操作为true,修改。如果不一样,false,本次修改失败,重新获取主物理内存的值。

/**
 * @Author leslie
 * @Date 2021/9/10
 *
 * 1    CAS是什么?     ===>compareAndSet
 *      比较并交换
 */
public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);

        //main do thing ...
        System.out.println(atomicInteger.compareAndSet(5,10)+"\t current data:"+atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5,20)+"\t current data:"+atomicInteger.get());
    }
}


2.CAS底层原理?如果知道,谈谈你对UnSafe的理解


  • atomicInteger.getAndIncrement()

  • UnSafe


  • CAS(自旋)是什么

  • unsafe.getAndAddInt



3.CAS缺点

  • 循环时间长开销很大。
  • 只能保证一个共享变量的原子操作。
  • 引发出来ABA问题?

三、原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?


1.ABA问题怎么产生的


2.原子引用

class User{
    String userName;
    int age;

    public User(String userName, int age) {
        this.userName = userName;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", age=" + age +
                '}';
    }
}
public class AtomicReferenceDemo {
    public static void main(String[] args) {
        User z3 = new User("z3",20);
        User li4 = new User("li4",30);
        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(z3);
        System.out.println(atomicReference.compareAndSet(z3,li4)+"\t"+atomicReference.get().toString());
        System.out.println(atomicReference.compareAndSet(z3,li4)+"\t"+atomicReference.get().toString());
    }
}

3.ABA问题的解决

public class ABADemo {//ABA问题的解决  AtomicStampedReference
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args) {
        System.out.println("====以下ABA问题的产生====");
        new Thread(()->{
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
        },"t1").start();
        new Thread(()->{
            //暂停1秒钟t2线程,保证上面的t1线程完成一次ABA操作
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(atomicReference.compareAndSet(100,2020)+"\t"+atomicReference.get());
        },"t2").start();

        //暂停一会线程
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("====以下ABA问题的解决====");


        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t 第1次版本号:"+stamp);
            //暂停1秒钟t3线程
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 第2次版本号:"+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 第3次版本号:"+atomicStampedReference.getStamp());
        },"t3").start();

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t 第1次版本号:"+stamp);
            //暂停3秒钟t4线程,保证上面的t3线程完成一次ABA操作
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean result = atomicStampedReference.compareAndSet(100, 101, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t修改成功否:"+result+"\t当前最新实际版本号:"+atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName()+"\t当前最新实际值:"+atomicStampedReference.getReference());
        },"t4").start();
    }
}


四、我们知道ArrayList是线程不安全,请编写一个不安全的案例并给出解决方案。

  • java.util.ConcurrentModificationException

1.解决方案一(new Vector、Collections.synchronizedList(new ArrayList<>()))


2.限制不可以使用Vector和Collections工具类解决方案2

3.集合不安全值写时复制CopyOnWriteArrayList

CopyOnWriteArrayList
写时复制
CopyOnWrite容器即写时复制容器。往一个容器添加元素的时候,不直接往当前容器object[ ]添加,而是先将当前容器object[ ]进行copy,再将元容器的引用指向新的容器setArray(newElements)。这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

/**
 * 集合不安全的问题
 * ArrayList
 */
public class ContainerNotSafeDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        List<String> list1 = Collections.synchronizedList(new ArrayList<>());
        List<String> list2 = new CopyOnWriteArrayList<>();
        for (int i = 1; i <=30 ; i++) {
            new Thread(()->{
                list2.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
        /**
         * 1.故障现象
         *          java.util.ConcurrentModificationException
         * 2.导致原因
         *      并发争抢修改导致,参考我们的花名册签名情况。
         *      一个人正在写入,另外一个同学过来抢夺,导致数据不一致异常。并发修改异常
         *
         * 3.解决方案
         *      3.1 new Vector()
         *      3.2 Collections.synchronizedList(new ArrayList<>())
         *      3.3 new CopyOnWriteArrayList<>()
         * 4.优化建议(同样的错误不犯第2次)
         */
    }
}
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

4.集合不安全之Set

  • CopyOnWriteArraySet
public class ContainerNotSafeDemo {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        Set<String> set1 = Collections.synchronizedSet(new HashSet<>());
        Set<String> set2 = new CopyOnWriteArraySet<>();
        for (int i = 1; i <=30 ; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

5.集合类不安全之Map


public class ContainerNotSafeDemo {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        Map<String,String> map1 = Collections.synchronizedMap(new HashMap<>());
        Map<String,String> map2 = new ConcurrentHashMap<>();
        for (int i = 1; i <=30 ; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

五、公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁


1.公平和非公平锁。

  • 是什么

  • 两者区别

  • 题外话

2.可重入锁(又名递归锁)

  • 是什么
  • ReentrantLock和synchronized就是一个典型的可重入锁
  • 可重入锁最大的作用就是避免死锁

3.自旋锁

/**
 * 题目:实现一个自旋锁
 * 自旋锁好处:循环比较获取知道成功为止,没有类似wait的阻塞。
 * 
 * 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有5秒钟,B随后进来发现
 * 当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到
 */
public class SpinLockDemo {
    // 原子引用线程
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t come in O(∩_∩)O");
        while (!atomicReference.compareAndSet(null,thread)){
        }
    }
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"\t invoked myUnlock()");
    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();
        new Thread(()->{
            spinLockDemo.myLock();
            //暂停一会线程
            try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
            spinLockDemo.myUnLock();
        },"AA").start();

        //暂停一会线程,保证AA线程先启动
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        new Thread(()->{
            spinLockDemo.myLock();
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            spinLockDemo.myUnLock();
        },"BB").start();
    }
}

4.独占锁(写锁)/共享锁(读锁)/互斥锁




六、CountDownLatch/CyclicBarrier/Semaphore使用过吗?

文章地址链接


七、阻塞队列知道吗?

阻塞队列文章链接


八、线程池用过吗?ThreadPoolExcecutor谈谈你的理解?

线程池文章链接


九、死锁编码及定位分析?


1. 是什么


2.死锁产生的原因?

  • 系统资源不充足
  • 进程运行推进的顺序不合适
  • 资源分配不当

3.代码

class HoldLockThread implements Runnable{
    private String lockA;
    private String lockB;

    public HoldLockThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockA+"\t 尝试获得"+lockB);
            try{ TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockB+"\t 尝试获得"+lockA);
            }
        }

    }
}
public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new HoldLockThread(lockA, lockB),"ThreadA").start();
        new Thread(new HoldLockThread(lockB,lockA),"ThreadB").start();
    }
}


4.解决




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值