多线程编程

约 814 字大约 3 分钟

多线程编程

aqs

aqs设计理念推荐看文档open in new window,论文中很容易看出设计围绕四个方向(synchronized、Thread.suspend、等待集、Object.wait、Object.signal,设计实现细节,推荐看源码。 整体来讲juc是把java内置锁从底层jvm层面拿到代码层面进行优化。

  • cas修改状态代替synchronized的获取
  • LockSupport代替阻塞
  • clh锁代替等待集,自旋代替上下文切换
  • 条件队列颗粒度优化Object.wait、signal

synchronize state

synchronize state = synchronizedmonitorenter、monitorexit使用状态协议字段替换,例如以下代码:

public class Sy {
    private static final Lock lock = new ReentrantLock();

    void testSynchronized() {
        synchronized (Sy.class) {
            System.out.println("test sy");
        }
    }

    void testLock() {
        lock.lock();
        try {
            System.out.println("test sy");
        } finally {
            lock.unlock();
        }
    }
}

代码编译和查看字节码,从字节码可以看出synchronized使用jvm内置锁,也就是monitorenter、monitorexit,而juc中的lock并没有使用内置锁

javac Sy.java
javap -v Sy

void testSynchronized();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // class com/sona/juc/Sy
         2: dup
         3: astore_1
         // 内置锁进入
         4: monitorenter
         5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #4                  // String test sy
        10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: aload_1
        // 内置锁退出
        14: monitorexit
        15: goto          23
        18: astore_2
        19: aload_1
        // 内置锁退出
        20: monitorexit
        21: aload_2
        22: athrow
        23: return
      Exception table:
         from    to  target type
             5    15    18   any
            18    21    18   any
      LineNumberTable:
        line 10: 0
        line 11: 5
        line 12: 13
        line 13: 23
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 18
          locals = [ class com/sona/juc/Sy, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

  void testLock();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=2, locals=2, args_size=1
         0: getstatic     #6                  // Field lock:Ljava/util/concurrent/locks/Lock;
         3: invokeinterface #7,  1            // InterfaceMethod java/util/concurrent/locks/Lock.lock:()V
         8: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: ldc           #4                  // String test sy
        13: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        16: getstatic     #6                  // Field lock:Ljava/util/concurrent/locks/Lock;
        19: invokeinterface #8,  1            // InterfaceMethod java/util/concurrent/locks/Lock.unlock:()V
        24: goto          38
        27: astore_1
        28: getstatic     #6                  // Field lock:Ljava/util/concurrent/locks/Lock;
        31: invokeinterface #8,  1            // InterfaceMethod java/util/concurrent/locks/Lock.unlock:()V
        36: aload_1
        37: athrow
        38: return
      Exception table:
         from    to  target type
             8    16    27   any
      LineNumberTable:
        line 16: 0
        line 18: 8
        line 20: 16
        line 21: 24
        line 20: 27
        line 21: 36
        line 22: 38
      StackMapTable: number_of_entries = 2
        frame_type = 91 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 10 /* same */

lock使用cas方式更新aqs内的一个状态state为契约代表锁的获取,释放,详情可以看aqs

Block

阻塞是代替Thread.suspend、Thread.resume,使用是LockSupport.park、LockSupport.unpark,详情可以看LockSupport

clh锁

clh锁代替内置等待集,并且每个节点不通信,减少通信带来的复杂程度,使用自旋减少上下文切换。详情可以看clh,自旋代码可以查看AbstractQueuedSynchronizer.acquireQueued

条件队列

条件队列颗粒度优化Object.wait、signal,详情见condition

应用

如果把aqs当作基石,在其基础上构建的应用主要分为两种,一是基于数据的,另外一个是基于任务的

  • 基于数据:BlockingDeque、BlockingQueue
  • 基于任务:ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool

数据类型

关于数据类型应用,可以参考condition

基于任务

关于数据类型应用,可以参考PoolExecutor

总结

aqs构建了底层基石,在此基础上构建了数据类型Queue和任务类型应用PoolExecutor,如果把aqs当作服务端,那么QueuePoolExecutor相对而言属于客户端。