Java源码解读系列2—Unsafe类(JDK1.7 )

本文详细介绍了Java中的Unsafe类,揭示了其如何提供对底层内存和对象字段的直接访问,包括初始化方法、操纵对象属性、操作堆外内存、CAS锁、线程阻塞与唤醒以及可见性保证。通过实例展示了Unsafe在并发编程中的应用,如并发容器的实现原理,同时也探讨了ABA问题及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 概述

Java与C语言一个较大差别是JVM屏蔽底层细节,使我们开发能够更专注于业务实现。
Unsafe类,顾名思义就说非安全类,属于sun.misc包下,是java开放给用户直接接触底层。网上大部分文章大部分直接讲解API,楼主这篇文章是通过应用场景去讲解Unsafe类,并且每个用途都附上使用用例,不仅教会你懂原理,还让你会使用。但还是由衷告诫你,除非是用来开发基础框架,否则不推荐使用。

2 初始化方法

在ConcurrentHashMap的源码中,我们可以看到Unsafe的示例化方法

private static final sun.misc.Unsafe U;
 static {
        try {
            U = sun.misc.Unsafe.getUnsafe();
         } catch (Exception e) {
            throw new Error(e);
        }
  }

但实际使用这段代码进行示例化时,会抛出非安全异常,这是神马操作?

Exception in thread "main" java.lang.SecurityException: Unsafe
	at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)

点进去看源码,原来是类加载问题。JUC包属于rt.jar,是由根类加载器进行加载,因此不会报错。对类加载不熟的同学可以参考《深入浅出JVM系列1:类加载器及其用法》

构造函数私有化
private Unsafe() {
    }

  @CallerSensitive
 public static Unsafe getUnsafe() {
       //获取调用的类
        Class var0 = Reflection.getCallerClass();
      // 根类加载器一般使用null来表示,而自定义类的类加载器为应用类加载器,因此两者不相等
        if (var0.getClassLoader() != null) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

通过getUnsafe方法获取theUnsafe属性这条路行不通,那么我们只能使用必杀技—反射

public class UnsafeApp {
        private static Unsafe unsafe;


    /**
     * 获取Unsafe类的一个实例
     */
    static {
        try {
            //getDeclaredFiled 获取类本身的属性成员
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            //私有对象必须设置为true,否则会报错
            f.setAccessible(true);
            unsafe = (Unsafe)f.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    }

3 操纵对象得属性

提到操作对象的私有属性,基本会使用反射。我们通过Unsfa类的objectFieldOffset方法也能实现获取对象的私有属性

/**
 * 学生类
 */
public class Student {
   //编号
    private int id;
    //姓名
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
}

public class UnsafeApp {

   ...省略unsafe示例方法...
   
    public static void main(String[] args) throws  Exception{
       /*
         * 操作私有对象属性
         */
        Student student = new Student(); //unsafe修改私有变量
        Field field = student.getClass().getDeclaredField("id");//getDeclaredField可以获取私有的变量

        //方法1:采用反射修改私有变量
        field.setAccessible(true); //为true时可以访问私有类型变量
        field.set(student, 111);
        System.out.println("student.id: " + student.getId());
        }
        
         //方法2:采用unsafe修改私有变量
        unsafe.putInt(student, unsafe.objectFieldOffset(field), 20);
        System.out.println("student.id: " + student.getId());
   }       

打印结果

student.id: 111
student.id: 20

4 操作堆外内存

JVM通过垃圾回收机制管理Java内存,使我们不需要像C语言一样,每次使用完堆内存还要自己手动释放,否则会造成内存泄漏。Java其实也是可以操作JVM外的内存,称为堆外内存或直接内存,是通过Unsafe实现。
用Unsafe开辟的内存空间不占用JVM Heap空间,也不具有自动内存回收功能,需要自己手动释放。典型的用途是本地缓存缓存。本地缓存相等Redis等内存数据库更为方便快捷,但会给虚拟机带来GC压力,因此可以通过堆外内存将数据缓存起来,如Ehcache。

       /**
         * 操作堆外内存
         */
        byte[] bigObject = "大对象".getBytes();

        //分配堆外内存
        //返回对外内存的地址
        long address = unsafe.allocateMemory(bigObject.length);

        //添加元素到指定位置
        for (int i = 0; i < bigObject.length; i++) {
            unsafe.putByte(address + i, bigObject[i]);
        }

        //获取指定位置的元素
        byte[] byteArray = new byte[bigObject.length];
        for (int i = 0; i < bigObject.length; i++) {
            byteArray[i] = unsafe.getByte(address + i);
        }

        System.out.println(new String(byteArray));
        

打印结果

大对象

5 CAS锁

5.1 原子操作

JDK1.6之前,多线程抢占资源时,使用synchonized关键字会触发系统调用,让没抢到锁的资源进入阻塞状态,后面获得资源后才恢复为RUNNABLE状态,这个操作过程涉及到用户态和内核态的切换,代价比较高。CAS(Compare And Swap,比较并替换)是一个原子操作,需要CPU支持。CAS更新一个变量的时候,只有当变量的预期值expect和地址偏移量offset当中的实际值相同时,才会将offset对应的expect修改为update。

*
* @param obj     需要更新的对象
* @param offset  obj中内存地址的偏移量
* @param expect 旧的值
* @param update 期望值
* @return 更新成功返回true
*/
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);

5.2 线程安全

CAS是原子操作,JUC包中并发容器中的底层很多就是使用CAS,如AtomicInteger等,被广泛用于并发编程。
这里模拟两个计数器,一个非线程安全,一个使用CAS保证线程安全

 /**
 * 非安全版本计数器
 */
public class UnSafeCounter {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int get() {
        return count;
    }
}


/**
 * 线程安全版本计数器
 */
public class SafeCounter {
    private volatile int count = 0;
    private final static long valueOffset;
    private final static Unsafe unsafe;

    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
            valueOffset = unsafe.objectFieldOffset
                    (SafeCounter.class.getDeclaredField("count"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    public void increment() {
        //无限循环
        for (;;) {
            int current = get();
            int next = current + 1;
            //CAS操作
            if (unsafe.compareAndSwapInt(this, valueOffset, current, next))
                //原子更新成功,跳出无限循环
                break;
        }
    }


    public int get() {
        return count;
    }
}


 
// 测试代码
public static void main(String[] args) throws  Exception{
   ThreadPoolExecutor executor = new ThreadPoolExecutor(10,
                10,
                0,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>());

        int count = 10 * 10000;


        for(int i = 0; i < count; i++)
        executor.execute(new Runnable() {
            @Override
            public void run() {
                unSafeCounter.increment();
            }
        });

        Thread.sleep(5000);

        System.out.println("UnSafeCounter.count: " + unSafeCounter.get());


        for(int i = 0; i < count; i++)
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    safeCounter.increment();
                }
            });



        Thread.sleep(5000);
        System.out.println("SafeCounter.count: " + safeCounter.get());


        //关闭线程池
        executor.shutdown();
        
}

打印结果:

UnSafeCounter.count: 99996
SafeCounter.count: 100000

这里有个大坑,线程池的getActiveCount()不准确问题,还是使用sleep方法,把时间设置长一点从而保证没有活跃线程

    while(executor.getActiveCount() != 0){
             Thread.sleep(1000);
       }

5.3 ABA问题

在并发环境下,线程A和线程B通过CAS在争夺资源,A通过CAS成功将预期值expect改为update后,此时,其他任务的线程C又将update改为expect,这时候B会误以为自己抢占到资源,从而产生脏数据,这就是ABA问题。

网上最常见的解决方法是加个版本号,每次将预期值expect改为update后,版本号就会发生变化。
public class AtomicStampedReferenceApp {

public class AtomicStampedReferenceApp {



      public static void main(String[] args) throws Exception {

        String expert = "gz";
        String update = "sz";
        int initversion = 1;

        AtomicStampedReference<String> reference = new AtomicStampedReference<String>(expert,1);

        //从gz去sz,现在又回到gz
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                reference.compareAndSet(expert,update,initversion,reference.getStamp()+1);
                System.out.println("从"+ expert + "去"  + reference.getReference() + ",现在的版本号:" + reference.getStamp());

                reference.weakCompareAndSet(update,expert,reference.getStamp(), reference.getStamp()+1);
                System.out.println("现在回到" + reference.getReference() + ",现在的版本号:" + reference.getStamp());
            }
        });
      t1.start();
      Thread.sleep(1000);

      //不使用版本做限制,存在aba问题
      Thread t2 = new Thread(new Runnable() {
          @Override
          public void run() {
              boolean ret = reference.compareAndSet(expert,update, reference.getStamp(),reference.getStamp()+1);
              System.out.print(ret? "我没有离开gz" : "我有离开过gz" );
              System.out.println(",现在的版本号:" + reference.getStamp());
          }
      });
      t2.start();
      Thread.sleep(1000);


      //使用指定版本号,发现已经发生变化,CAS更新失败,解决了aba问题
      Thread t3 = new Thread(new Runnable() {
          @Override
          public void run() {
              boolean ret = reference.compareAndSet(expert,update, initversion,reference.getStamp()+1);
              System.out.print(ret? "我没有离开gz" : "我有离开过gz" );
              System.out.println(",现在的版本号:" + reference.getStamp());
          }
      });
      t3.start();

    }

打印结果

从gz去sz,现在的版本号:2
现在回到gz,现在的版本号:3
我没有离开gz,现在的版本号:4
我有离开过gz,现在的版本号:4

如果看过AQS源码,里面还提供一种解决ABA的方法。A,B,C三个线程执行CAS,线程A抢占成功将同步状态从0更新为1,失败的线程被放到FIFO队列中。
后面A处理完将同步状态更新回0,如果此时刚好有线程D执行CAS,不就会获得资源,相当于插队,这不就堆B和C不公平?AQS提供的解决方法是判断队列中是否有线程在排队,如果有则不执行CAS。

6 阻塞和唤醒线程

6.1 阻塞和唤醒的实现原理

JUC中,当一个线程需要等待某个操作时,通过Unsafe的park()方法来阻塞此线程。当线程需要再次运行时,通过Unsafe的unpark()方法来唤醒此线程。如LockSupport.park()/unpark(),它们底层都是调用的Unsafe的这两个方法。

//阻塞线程
public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    
 //唤醒线程
 public static void unpark(Thread thread) {
         if (thread != null)
             UNSAFE.unpark(thread);
  }

6.2 阻塞和唤醒的用例

根据源码,我们可以模拟线程的阻塞和唤醒

Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                long startTime = System.currentTimeMillis();
                System.out.println("线程" + Thread.currentThread().getName() + "开始阻塞");
                
                 //第一个参数为true时,第二个参数使用单位为毫秒的绝对时间,表示要阻塞到某个时间点,如unsafe.park(true, System.currentTimeMillis() + 1000)表示阻塞1秒
                  //第一个参数为false时,第二个参数使用单位为纳秒的绝对时间,表示要阻塞的时间间隔, 0L表示永久阻塞,如 unsafe.park(false, 1000000000l)表示阻塞1秒
                unsafe.park(false, 0L); 
                System.out.println("线程" + Thread.currentThread().getName() + "被唤醒");
                long endTime = System.currentTimeMillis();
                System.out.println("阻塞时间为:" + (endTime - startTime) / 1000 + "秒");
            }
        });

        t1.setName("park_test_thread");
        t1.start();
        Thread.sleep(3000);            
        unsafe.unpark(t1);

打印结果

线程park_test_thread开始阻塞
线程park_test_thread被唤醒
阻塞时间为:3秒

6.3 破坏阻塞的场景

在调用park()之前调用了unpark或者interrupt则park阻塞失效。

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {

//                unsafe.unpark(Thread.currentThread());
                Thread.currentThread().interrupt();
                long startTime = System.currentTimeMillis();
                System.out.println("线程" + Thread.currentThread().getName() + "开始阻塞");
                unsafe.park(false, 0L);
                System.out.println("线程" + Thread.currentThread().getName() + "被唤醒");
                long endTime = System.currentTimeMillis();
                System.out.println("阻塞时间为:" + (endTime - startTime) / 1000 + "秒");
            }
        });


        t1.setName("park_test_thread");
        t1.start();
        Thread.sleep(3000);
        unsafe.unpark(t1);

打印结果

线程park_test_thread开始阻塞
线程park_test_thread被唤醒
阻塞时间为:0秒

7 可见性

先了解下可见性的使用场景

public class CounterThread extends Thread {
    private   Object isContinue = "true";
    private final static long valueOffset;
    private final static Unsafe unsafe;

    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
            valueOffset = unsafe.objectFieldOffset
                    (CounterThread.class.getDeclaredField("isContinue"));
        } catch (Exception ex) { throw new Error(ex); }
    }


    public void setContinue(Object isContinue) {
        this.isContinue = isContinue;
    }

    public Object isContinue() {
        return isContinue;
    }

    @Override
    public void run() {
        System.out.println("开始统计");
        while (isContinue == "true")
        {

        }
        System.out.println("结束统计");
    }
    
    public class TestCounter {

    public static void main (String[] args) throws Exception{
        CounterThread thread = new CounterThread();
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.setContinue("false");
    }
}

打印结果

开始统计

线程一直处于运行状态,是因为线程每次都是从私有堆栈中拿取isContinue的值,而不是从公共堆栈中获取,导致isContinue的值发生改变,但是线程没响应。
使用Unsafe中的getObjectVolatile方法,线程会立即获取公共堆栈中最新的值。

   public void setContinue(Object isContinue) {
        this.isContinue = isContinue;
    }
    
      @Override
    public void run() {
        System.out.println("开始统计");
        while (unsafe.getObjectVolatile(this, valueOffset) == “true”)
        {
        }
        System.out.println("结束统计");
    }

打印结果

开始统计
结束统计

8 操作数组

Unsafe通过获取数组的起始偏移量和每个元素空间大小,从而直接操作数组,其中结合位运算的方法比乘法运算的效率会更高。

        int[] array = new int[20];
        //数组第一个元素的偏移地址
        int baseOffset = unsafe.arrayBaseOffset(array.getClass());

        //数组中每个元素的空间大小
        int indexScale = unsafe.arrayIndexScale(array.getClass());

        for (int i = 0; i < array.length; i++){
            //数组中第i个元素的偏移量
            //方法1 基于乘法运算
//            long offset = baseOffset +(i * indexScale);

            //方法2 基于位运算

            //numberOfLeadingZeros表示从最左边开始数起连续的0的个数为,如 numberOfLeadingZeros(1)的值为31
            int ssfit = 31 - Integer.numberOfLeadingZeros(indexScale);
            long offset = (i << ssfit) + baseOffset;

            unsafe.putInt(array, offset, i);
        }

        for (int i = 0; i < array.length; i++){
            System.out.println("元素位置:" + i + " 值:" + array[i]);
        }

打印结果

元素位置:0 值:0
元素位置:1 值:1
元素位置:2 值:2
元素位置:3 值:3
元素位置:4 值:4

其中这段代码比较难理解。那么它具有普适性吗?答案是否。

  int ssfit = 31 - Integer.numberOfLeadingZeros(indexScale);
  long offset = (i << ssfit) + baseOffset;

先看下测试用例

 int num1 = 7;
 System.out.println(1 << (31 - Integer.numberOfLeadingZeros(num1)));
 System.out.println(1 * num1);

 int num2 = 8;
 System.out.println(1 << (31 - Integer.numberOfLeadingZeros(num2)));
 System.out.println(1 * num2);

打印结果

4
7
8
8

从结果可以看出这段代码不具备普适性,有且只有当num为2^n时才能成立。
这就好理解,即一个数i * 2^n等价于 i << n

9 参考文献

  1. JDK7在线文档
    https://tool.oschina.net/apidocs/apidoc?api=jdk_7u4
  2. Bruce Eckel,Java编程思想 第4版. 2007, 机械工业出版社ConcurrentHashMap
<think>嗯,用户之前让我介绍过Java多线程的基础知识,现在他们想深入了解JUC(java.util.concurrent)的源码。这说明用户可能已经掌握了多线程的基本概念和使用方法,现在想更深入理解底层实现,提升自己的技术水平。 首先,我需要确定用户对JUC的了解程度。他们可能已经用过一些工具,比如ReentrantLock或CountDownLatch,但现在想了解这些内部的机制。用户可能是中高级开发者,希望优化代码性能,或者准备面试,需要深入理解并发框架。 接下来,我需要考虑如何组织内容。直接深入源码可能会让用户感到困惑,所以应该先从JUC的整体结构入手,然后分模块解析核心。比如,原子、锁、同步器、并发集合、线程池等,每个部分都需要挑选关键点进行讲解。 用户可能需要知道关键设计模式,比如AQS(AbstractQueuedSynchronizer),因为这是JUC的核心。需要详细解释AQS的工作原理,如CLH队列、状态管理等。此外,CAS操作和volatile关键字也是实现无锁并发的基础,需要结合源码说明它们的作用。 另外,用户可能对常见的实现感兴趣,比如ReentrantLock的公平锁与非公平锁区别,ConcurrentHashMap的分段锁或CAS优化,线程池的任务调度流程等。需要结合代码示例和流程图,帮助用户理解这些复杂机制。 同时,要注意避免过于冗长的代码展示,而是聚焦在关键方法和设计思路上。例如,AQS的acquire和release方法,以及它们如何被ReentrantLock或Semaphore调用。此外,可以比较不同版本的实现变化,比如JDK1.71.8中ConcurrentHashMap的改进,让用户了解优化方向。 最后,需要给出学习建议,推荐用户如何逐步深入源码,比如从简单的AtomicInteger开始,再逐步研究AQS和锁的实现。同时,提醒用户注意调试和测试的重要性,通过实际案例加深理解。 总结下来,回答的结构应该是先概述JUC的重要性,再分模块解析核心源码,结合关键设计模式和具体实现,最后给出学习方法和资源推荐。这样既系统又深入,帮助用户由浅入深地掌握JUC源码。</think>好的!`java.util.concurrent`(JUC)是 Java 并发编程的核心工具包,包含大量高性能、线程安全的工具和框架。深入源码理解其设计思想,能帮助你掌握高并发编程的精髓。以下从 **核心模块**、**设计模式**、**关键源码分析** 三个维度进行详细解读。 --- ### **一、JUC 整体架构** JUC 的核心模块包括: 1. **原子**(Atomic):基于 CAS 的无锁并发操作(如 `AtomicInteger`)。 2. **锁与同步器**(Locks):如 `ReentrantLock`、`AQS`(AbstractQueuedSynchronizer)。 3. **并发容器**(Collections):如 `ConcurrentHashMap`、`CopyOnWriteArrayList`。 4. **线程池**(Executors):`ThreadPoolExecutor`、`ForkJoinPool`。 5. **同步工具**(Tools):`CountDownLatch`、`CyclicBarrier`、`Semaphore`。 6. **并发流与 Future**:如 `CompletableFuture`、`ForkJoinTask`。 --- ### **二、核心源码解读** #### **1. AQS(AbstractQueuedSynchronizer)** AQS 是 JUC 锁和同步器的基石(如 `ReentrantLock`、`Semaphore` 均基于它实现)。 ##### **核心设计** - **状态变量(state)**:通过 `volatile int state` 表示资源状态(如锁的持有次数)。 - **CLH 队列**:一个双向链表实现的等待队列,存储竞争资源的线程。 - **模板方法模式**:子只需实现 `tryAcquire`/`tryRelease`,AQS 负责线程排队和唤醒。 ##### **源码关键方法** ```java // 独占模式获取资源(如 ReentrantLock.lock()) public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // 子需实现 tryAcquire(如判断 state 是否为0) protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } ``` ##### **实现示例:ReentrantLock 的公平锁** ```java // 公平锁的 tryAcquire 实现 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && // 关键:检查队列是否有等待线程 compareAndSetState(0, acquires)) { // CAS 更新状态 setExclusiveOwnerThread(current); return true; } } // 处理重入逻辑(state 增加) else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; setState(nextc); return true; } return false; } ``` --- #### **2. 原子(AtomicInteger)** 原子的核心是 **CAS(Compare-And-Swap)** 操作,直接依赖 CPU 指令(如 x86 的 `cmpxchg`)。 ##### **源码关键实现** ```java public class AtomicInteger extends Number implements java.io.Serializable { private volatile int value; // 通过 volatile 保证可见性 public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } } // Unsafe 的 CAS 操作(native 方法) public final class Unsafe { public final native boolean compareAndSwapInt( Object o, long offset, int expected, int x); } ``` ##### **CAS 的 ABA 问题** - **问题**:变量从 A 改为 B 又改回 A,CAS 无法感知中间变化。 - **解决方案**:使用 `AtomicStampedReference` 添加版本号。 --- #### **3. ConcurrentHashMap(JDK 1.8)** JDK 1.8 摒弃分段锁,改用 **CAS + synchronized** 实现更细粒度的锁。 ##### **关键设计** - **Node 数组(table)**:每个桶位(bucket)是一个链表或红黑树。 - **并发控制**: - **CAS 初始化数组**:避免多线程重复初始化。 - **synchronized 锁桶头节点**:仅锁定单个桶,减少竞争。 ##### **putVal 方法核心逻辑** ```java final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable(); // CAS 初始化数组 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) break; // CAS 插入新节点 } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); // 协助扩容 else { synchronized (f) { // 锁住桶头节点 // 插入链表或红黑树 } } } } ``` --- #### **4. ThreadPoolExecutor** 线程池的核心是 **任务队列** 和 **线程调度策略**。 ##### **核心参数** - **corePoolSize**:核心线程数(即使空闲也不销毁)。 - **workQueue**:任务队列(如 `LinkedBlockingQueue`)。 - **RejectedExecutionHandler**:拒绝策略(如丢弃、抛异常)。 ##### **execute 方法逻辑** ```java public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) // 创建新线程执行任务 return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { // 任务入队 // 二次检查线程池状态 } else if (!addWorker(command, false)) // 尝试创建非核心线程 reject(command); // 触发拒绝策略 } ``` --- ### **三、JUC 设计模式与技巧** 1. **CAS 无锁编程** - 通过 `Unsafe` 直接操作内存,避免锁竞争(如原子、ConcurrentHashMap)。 - 适用场景:读多写少,竞争不激烈。 2. **模板方法模式** - AQS 将公共逻辑(如线程排队)抽象到基,子只需实现特定方法(如 `tryAcquire`)。 3. **写时复制(Copy-On-Write)** - `CopyOnWriteArrayList` 在修改时复制整个数组,保证读操作无锁。 4. **分段锁优化** - JDK 1.7 的 `ConcurrentHashMap` 使用分段锁(Segment),减少锁粒度。 --- ### **四、高频源码面试题** 1. **AQS 的 CLH 队列为什么是双向链表?** - 答:为了支持取消操作(如超时或中断),需要快速定位前驱节点。 2. **ConcurrentHashMap 1.71.8 的区别?** - 1.7:分段锁 + ReentrantLock; - 1.8:CAS + synchronized + 红黑树。 3. **线程池如何回收空闲线程?** - 答:`ThreadPoolExecutor` 通过 `getTask()` 中的 `poll(keepAliveTime)` 实现超时回收。 --- ### **五、学习建议** 1. **调试源码**:通过 IDE 调试 `ReentrantLock.lock()` 的执行流程。 2. **阅读注释**:JUC 源码注释通常包含设计思路和并发规则。 3. **手写简化版**:尝试实现一个简单的 AQS 或线程池。 理解 JUC 源码不仅能提升并发编程能力,还能深入掌握 Java 的高性能设计哲学。建议从 `AtomicInteger` 和 `ReentrantLock` 入手,逐步扩展到复杂如 `ConcurrentHashMap` 和 `ThreadPoolExecutor`。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值