多线程与并发5 AQS源码分析、VarHandle、ThreadLocal源码 、强软弱虚四种引用

https://www.jianshu.com/p/497a8cfeef63// ReentrantLock构造函数传入了一个内部类 NonfairSync 

NonfairSync-》Sync-》AbstractQueuedSynchronizer 继承关系
public ReentrantLock() {
    sync = new NonfairSync();
}

//上锁也是调用NonfairSync 非公平锁
public void lock() {
    sync.lock();
}

//进入NonefairSync类的内部
final void lock() {
if (compareAndSetState(0, 1)) //获得锁 cas setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } stateOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
这里,对state的偏移量中的内存进行CAS操作,设置为1,表明第一次获取锁。AQS中,state是一个volatile变量,表示线程获取锁的次数,state为0,表示没有获取锁,state大于1,则表示重入锁获取的次数。执行完compareAndSetState第一次获取锁之后,执行setExclusiveOwnerThread,在AbstractOwnableSynchronizer类中设置成员变量Thread exclusiveOwnerThread为当前线程,表示这个线程独占了这个锁。
如果第二次调用lock()获取锁,由于内存中state不为0,cas失败,则进入else执行acquire(1);
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //没有获得锁就acquireQueued 没有获得锁就放入队列中 cas放入队列末端
        selfInterrupt();
}
//tryAcquire是一个抽象方法,NonfairSync中的实现如下:
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) { 
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 得到state数量  state是一个用volatile修饰的整型数值  当获得锁之后会变成1 释放后又会变成0  有一个双向链表监控着这个state,里面全是线程。 
    int c = getState();
    // 没有获取过锁
    if (c == 0) {
        // 获取一次,cas设置state为acquires,也就是1
        if (compareAndSetState(0, acquires)) { //acquire(1)
            // 设置拥有者线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        // 增加重入次数
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 设置state,这里不需要同步,因为已经是统一线程第二次获取锁,也是在本线程里获取锁,没有线程安全问题,第一次调用的state和ownerThread一定是可见的
        setState(nextc);
        return true;
    }
    return false;
}
没有获得锁就acquireQueued 没有获得锁就放入队列中 cas操作放入队列末端。为什么是双向链表呢,因为需要知道前面节点的状态,前面线程持有锁,当前节点就尝试获得锁,如果获不到就阻塞,等待前置节节点释放后就叫醒后面的线程,否则阻塞。
  final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

VarHandle 

https://blog.csdn.net/sench_z/article/details/79793741

ThreadLocal 

ThreadLocal的set(T value)方法   ThreadLocal里有个getMap(Thread t)方法 可以获取当前线程的Map 后把值放入该map中 ,k(弱引用)指向的是this,即ThreadLocal。所以每一个线程对应一个自己的Map。为什么是弱引用,因为tl=threadlocal 当方法体结束后,就会被释放。  map在Thread类中

为什么需要ThreadLocal呢? 因为保证同一线程下不同方法间共享同一个值,比如数据库的连接对象Connection.。 从连接池里取出一个Connection放入ThreadLocal,后面方法需用时,从该ThreadLocal取出即可。

简单概括过程:有ThreadLocal对象 tl,线程 t 调用 tl.get(), 则去线程 t 的ThreadLocalMap属性对象里找到一个entry,若entry.key == tl返回true,则此entry是目标entry,此entry.value就是我们的目标。

  public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取当前线程的map
        if (map != null)
            map.set(this, value);//把值放入该线程的map里
        else
            createMap(t, value);
    }
  • 强引用  只要存在引用指向该对象 则该对象永远不会被回收

public class NormslReference {
    public static void main(String[] args) throws IOException {
        Demo demo = new Demo();
        demo = null;
        System.gc(); 
        System.in.read();//阻塞当前主线程 因为System.gc 不在主线程中
    }
}
class Demo{
    @Override
    protected void finalize() throws Throwable {
        System.out.println("该对象要被回收");
    }
}
  • 软引用  该对象被软引用指向后,内存不足时就会被回收。

//运行前要设置虚机内存
public class SoftReferenceTest {
    public static void main(String[] args) throws InterruptedException {
        SoftReference<byte[]>  soft = new SoftReference<>(new byte[1024*1024*5]);//分配一个5MM的字节数组
        System.out.println(soft.get());

        System.gc();

        TimeUnit.SECONDS.sleep(2);
        System.out.println(soft.get());

        byte[] bytes = new byte[1024*1024*10];//有分配一个10M的字节数组

        System.out.println(soft.get());//之前被虚引用指向的字节数组会在内存不足时被回收
    }
}
  • 弱引用  只要遇到gc就会被回收

https://blog.csdn.net/qq_42862882/article/details/89820017

ThreadLocal为什么要使用弱引用和内存泄露问题

为什么要这么做呢?看下面的这种场景:

public void func1() {
        ThreadLocal tl = new ThreadLocal<Integer>(); //line1
         tl.set(100);   //line2
         tl.get();       //line3
}

line1新建了一个ThreadLocal对象,t1 是强引用指向这个对象;line2调用set()后,新建一个Entry,通过源码可知entry对象里的 k是弱引用指向这个对象。如图:

当func1方法执行完毕后,栈帧销毁,强引用 tl 也就没有了,但此时线程(正在运行的线程)的ThreadLocalMap里某个entry的 k 引用还指向这个对象。若这个k 引用是强引用,就会导致k指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏,但是弱引用就不会有这个问题(弱引用及强引用等这里不说了)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收,而且entry的k引用指向为null,此后我们调用get,set或remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。

概括说就是:在方法中新建一个ThreadLocal对象,就有一个强引用指向它,在调用set()后,线程的ThreadLocalMap对象里的Entry对象又有一个引用 k 指向它。如果后面这个引用 k 是强引用就会使方法执行完,栈帧中的强引用销毁了,对象还不能回收,造成严重的内存泄露。

虽然k=null 但vlaue指向的值访问不到了,此时该value永远这个值,Map越积越多,所以最后要使用tl.remove()

  • 虚引用

个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着弱引用关联着对象,那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null,弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。

jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存,而直接内存是在堆内存之外(其实是内存映射文件,自行去理解虚拟内存空间的相关概念),所以直接内存的分配和回收都是有Unsafe类去操作,java在申请一块直接内存之后,会在堆内存分配一个对象保存这个堆外内存的引用,这个对象被垃圾收集器管理,一旦这个对象被回收,相应的用户线程会收到通知并对直接内存进行清理工作。



原文地址:https://www.cnblogs.com/zdcsmart/p/12600740.html