一、父子线程怎么共享数据
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表现出了锁的特性。