知识总结 多线程

 

线程

  • 线程状态:new,runnable(running,ready),timeWaiting,waiting,blocked,Terminated
  • 启动方法:runnable.start(); new Thread(Runnable r);

synchronized

  • synchronized: 偏向(对象头),轻量(自旋,占cpu),重量(os,等待)
  • synchronized:锁对象,(this,xx.class)

    偏向锁

  • 当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

    轻量级锁

  • 线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;

  • 如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。

    重量级锁

  • 但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

字节码层面处理

  • 方法则增加访问标识 ACC_SYNCHRONIZED 0x0020 [synchronized]
  • 代码块增加 monitorenter 和 两个 monitorexit
    11 monitorenter
    12 getstatic #4 <java/lang/System.out>
    15 invokevirtual #5 <java/io/PrintStream.println>
    18 aload_2
    19 monitorexit
    20 goto 28 (+8)
    23 astore_3
    24 aload_2
    25 monitorexit
    

    jvm 层面实现

  • 调用系统的同步机制

    硬件层面实现

  • 基本都是调用 lock 指令

Condition

  • 代替不好用的 Object.await()
  • 需要 Lock.newCondition() 生成
  • 本质是等待队列,new 多个相当于多个不同的队列
  • 和object.await 方法需要synchronize对象 一样 ,操作前需要获取锁 .lock。 await 时会挂起线程释放锁

volatile

  • 保证线程可见性
    • cpu 缓存一致性协议
  • 禁止指令重排序
    • 单例双重检查锁案例
  • 不保证原子性
    • i++

锁优化

  • 锁细化
  • 锁粗化(竞争太频繁)

cas

  • compare and swap
  • atomic包 unsafe 类
  • cpu 原语
  • aba 问题
    • 时间戳/版本号 AtomicStampedReference 可以用版本号
  • LongAdder 分段锁cas

Unsafe

  • allocateMemory 直接分配内存
  • compareAndSetXX

AQS

  • Semaphore,ReentrantLock 等同步器都是内部定义了一个 Sync 类,继承于 AbstractQueuedSynchronizer
  • 内部有定义Node类,大量使用 VarHandle 实现 cas 操作
  • 需要实现 tryAcquire&tryRelease(排他锁) 或者 tryAcquireShared&tryAcquireShared(共享锁),addWaiter(Node mode)传参 Node.EXCLUSIVE for exclusive, Node.SHARED for shared

VarHandle

  • 能实现cas,直接操作二进制码,访问速度比反射快
      /** CASes next field. */
      final boolean compareAndSetNext(Node expect, Node update) {
          return NEXT.compareAndSet(this, expect, update);
      }
    
      final void setPrevRelaxed(Node p) {
          PREV.set(this, p);
      }
    
      // VarHandle mechanics
      private static final VarHandle NEXT;
      private static final VarHandle PREV;
      private static final VarHandle THREAD;
      private static final VarHandle WAITSTATUS;
      static {
          try {
              MethodHandles.Lookup l = MethodHandles.lookup();
              NEXT = l.findVarHandle(Node.class, "next", Node.class);
              PREV = l.findVarHandle(Node.class, "prev", Node.class);
              THREAD = l.findVarHandle(Node.class, "thread", Thread.class);
              WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class);
          } catch (ReflectiveOperationException e) {
              throw new ExceptionInInitializerError(e);
          }
      }
    

ThreadLocal

  • 使用了 Thread 对象的一个 map,key 是当前 ThreadLocal对象,value 是目标对象
      ThreadLocal<String> tl = new ThreadLocal<>();
      tl.set("hehe");
      tl.get();
      tl.remove();
    
      public void set(T value) {
          Thread t = Thread.currentThread();
          //t.threadLocals  Thread对象的一个成员变量 map
          ThreadLocalMap map = getMap(t); 
          if (map != null) {
              map.set(this, value);
          } else {
              createMap(t, value);
          }
      }
    

引用

强引用

    Object o = new Object();

弱引用

    WeakReference reference = new WeakReference(new Object());
  • 只要发生GC,就会被干掉。只要没有强引用指向对象,就应该被回收,一般用于容器中。
  • ThreadLocal 中 ThreadLocalMap的 Entry 即是该引用。为了防止内存泄漏。

软应用

    SoftReference reference = new SoftReference(new Object());
  • 内存不足时优先被回收,一般用于缓存

虚引用

    PhantomReference reference = new PhantomReference(new Object(), new ReferenceQueue());
  • 管理堆外内存,给 jvm 用的。 当对象被回收,会往 QUEUE 加入该值