• 新鲜出炉!看完这份多线程面试题,今年秋招我完全不慌


    一、父子线程怎么共享数据

    JDK的InheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把任务提交给线程池时的ThreadLocal值传递到任务执行时。

    核心类TransmittableThreadLocal:
    public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> { ...... }
    

      

    首先TransmittableThreadLocal继承自InheritableThreadLocal,这样可以在不破坏原有InheritableThreadLocal特性的情况下,还能充分使用Thread线程创建过程中执行init方法,从而达到父子线程传递数据的目的。

    变量holder源码如下:

    holder中存放的是InheritableThreadLocal本地变量

    WeakHashMap支持存放空置

    // 理解holder,需注意如下几点:
    // 1、holder 是 InheritableThreadLocal 变量;
    // 2、holder 是 static 变量;
    // 3、value 是 WeakHashMap;
    // 4、深刻理解 ThreadLocal 工作原理;
    private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder =
        new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
          @Override
          protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
              return new WeakHashMap<>();
          }
     
          @Override
          protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
              return new WeakHashMap<>(parentValue);
          }
    };
    

      

    主要方法源码如下:

    get方法调用时,先获取父类的相关数据判断是否有数据,然后在holder中把自身也给加进去

    set方法调用时,先在父类中设置,再本地判断是holder否为删除或者是新增数据

    remove调用时,先删除自身,再删除父类中的数据,删除也是直接以自身this作为变量Key

    // 调用 get() 方法时,同时将 this 指针放入 holder
    public final T get() {
        T value = super.get();
        if (null != value) {
            addValue();
        }
        return value;
    }
    void addValue() {
        if (!holder.get().containsKey(this)) {
            holder.get().put(this, null); // WeakHashMap supports null value.
        }
    }
    // 调用 set() 方法时,同时处理 holder 中 this 指针
    public final void set(T value) {
        super.set(value);
        if (null == value) { // may set null to remove value
            removeValue();
        } else {
            addValue();
        }
    }
    void removeValue() {
        holder.get().remove(this);
    }
    

      

    采用包装的形式来处理线程池中的线程不会执行初始化的问题,源码如下:

    先取得holder

    备份线程本地数据

    run原先的方法

    还原线程本地数据

    public void run() {
         Object captured = this.capturedRef.get():
         if (captured != null && (!this.releaseTtlValueReferenceAfterRun ||
                 this.capturedRef.compareAndSet(captured, (0bject) null))){
             Object backup = Transmitter.replay(captured);
             try {
                 this.runnable.run();
             } finally {
                 Transmitter.restore(backup);
             }
         }else{
             throw new IllegalStateException("TTL value reference is released after run!");
         }
    }
    

      

    处理线程池中的线程不会执行初始化的问题,备份方法:

    先获取holder中的数据

    进行迭代,数据在captured中不存在,但是holder中存在,说明是后来加进去的,进行删除

    再将captured设置到当前线程中

    public static Object repLay(@Nonnull Object captured) (
        Map<TransmittableThreadLocal<?>,Object>capturedMap = (Map) captured;
        Map<TransmittableThreadLocal<?>,Object>backup = new HashMap() ;
        Iterator iterator=((Map) TransmittableThreadLocal.holder.get()).entrySet().iterator();
     
        while(iterator.has Next() ) (
           Entry<TransmittableThreadLocal<?>,?>next = (Entry) iterator.next()
           TransmittableThreadLocal<?>threadLocal = (TransmittableThreadLocal)next.getKey();
           backup.put(threadLocal, threadLocal.get() ) ;
           if(!capturedMap.containsKey(thread Local) ) (
               iterator.remove() ;
               threadLocal.superRemove() ;
           )
     
        setTtlValuesTo(capturedMap) ;
        TransmittableThreadLocal.doExecuteCallback(true) ;
        return backup;
    )
    

      

    还原方法:

    先获取holder中的数据

    backup中不存在,holder中存在,说明是后面加进去的,进行删除还原操作

    再将backup设置到当前线程中

    public static void restore(@Nonnull0bject backup) (
        Map<TransmittableThreadLocal<?>,Object> backupMap = (Map)backup;
        TransmittableThreadLocal.doExecuteCallback(false)
        Iterator iterator=((Map) TransmittableThreadLocal.holder.get()).entrySet().iterator();
     
        while(iterator.has Next() ) (
            Entry<TransmittableThreadLocal<?>,?> next = (Entry)iterator.next();
            TransmittableThreadLocal<?> threadLocaL=(TransmittableThreadLocal)next.getKey();
            if(!backupMap.containsKey(threadLocal) ) (
                iterator.remove() ;
                threadLocal.superRemove() ;
            }
        |
        setTtlValuesTo(backupMap) ;
    }
    

      

    二、CountDownLatch和CyclicBarrier的异同

    1、相同点

    都可以实现线程间的等待

    2、不同点

    侧重点不同

    CountDownLatch:一般用于一个线程等待一组其它线程;计数器不可以重用

    CyclicBarrier:一般是一组线程间的相互等待至某同步点,计数器是可以重用的

    实现原理不同

    CyclicBarrier:如果有三个线程thread1、thread2和thread3,假设线程执行顺序是thread1、thread2、thread3,那么thread1、thread2对应的Node节点会被加入到Condition等待队列中,当thread3执行的时候,会将thread1、thread2对应的Node节点按thread1、thread2顺序转移到AQS同步队列中,thread3执行lock.unlock()的时候,会先唤醒thread1,thread1恢复继续执行,thread1执行到lock.unlock()的时候会唤醒thread2恢复执行

    CountDownLatch:使用CountDownLatch(int count)构造器创建CountDownLatch实例 ,将count参数赋值给内部计数 state,调 await() 法阻塞当前线程,并将当前线程封装加到等待队 中,直到state等于零或当前线程被中断;调 countDown() 法使state值减 ,如果state等于零则唤醒等待队中的线程

    三、AQS原理

    AQS是一个基于状态(state)的链表管理方式,reentracntlock这个锁是基于AQS实现的子类sync这个来完成锁。

    获取锁的时候,当前线程会去更新状态state的值,如果为0才去更新,通过CAS进行更新,如果成功更新为1,那么获取到锁,将锁的拥有者改成当前线程,如果失败,那么进行tryAcquire() 这个函数进行首先还是尝试更新state状态,反正开销也小,再次去尝试一次也行,如果尝试失败,那么去看看当前拥有锁的线程是不是当前线程,如果是,那么将state状态值加1,如果不是,那么将线程入阻塞队列,addWaiter函数,进行的话首先判断当前head是不是为空,为空尝试将当前的线程关联的节点用CAS加入队列,不为空或者加入失败,那么用CAS加入到队列的下一个节点。

    释放锁的时候,将状态值减1,如果状态值为0说明可以释放锁,如果结果状态为0,就将排它锁的Owner设置为null,以使得其它的线程有机会进行执行。

    四、volatile(指令重排序和内存屏障)

    1、什么是内存屏障

    内存屏障其实就是一个CPU指令,在硬件层面上来说可以扥为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。主要有两个作用:

    (1)阻止屏障两侧的指令重排序;

    (2)强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

    在JVM层面上来说作用与上面的一样,但是种类可以分为四种:

     
     
    2、volatile如何保证有序性

    首先一个变量被volatile关键字修饰之后有两个作用:

    (1)对于写操作:对变量更改完之后,要立刻写回到主存中。

    (2)对于读操作:对变量读取的时候,要从主内存中读,而不是缓存。

    现在针对上面JVM的四种内存屏障,应用到volatile身上。因此volatile也带有了这种效果。其实上面提到的这些内存屏障应用的效果,可以用happen-before来总结归纳。

    3、内存屏障分类

    内存屏障有三种类型和一种伪类型:

    (1)lfence:即读屏障(Load Barrier),在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据,以保证读取的是最新的数据。

    (2)sfence:即写屏障(Store Barrier),在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存,以保证写入的数据立刻对其他线程可见。

    (3)mfence,即全能屏障,具备ifence和sfence的能力。

    (4)Lock前缀:Lock不是一种内存屏障,但是它能完成类似全能型内存屏障的功能。

    为什么说Lock是一种伪类型的内存屏障,是因为内存屏障具有happen-before的效果,而Lock在一定程度上保证了先后执行的顺序,因此也叫做伪类型。比如,IO操作的指令,当指令不执行时,就具有了mfence的功能。

    由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性。



  • 相关阅读:
    java volatile关键字解惑
    Java 反射
    拷贝源实体类到目标实体类中
    Bean和Map之间的转换
    DateUtils时间的封装
    HttpClient的代码封装,便于直接调用
    HttpClient语法
    LinkedHashMap+ConcurrentHashMap+hashMap的区别
    1006 Tick and Tick
    Event Flow
  • 原文地址:https://www.cnblogs.com/lwh1019/p/12981556.html
Copyright © 2020-2023  润新知