线程
- 线程状态: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 加入该值
PREVIOUS知识总结 redis
NEXT知识总结 容器