线程模型
1:1线程模型用的比较多,用户线程调用频繁会影响内核线程,引用CAS避免部分状态切换(Java AQS函数级别锁),限制线程数量。(UT-用户线程;P-内核线程)
n:1线程模型存在的问题是一个用户线程占用了轻量级进程,其他用户线程不能使用。
m:n线程模型是结合上述两者优点。GO语言的GMP线程模型就是多对多。(Java的loom项目)
锁机制
在堆和方法区中需要人工加锁保证线程安全(蓝色部分)。
每个对象有一把锁,存放在对象头中,存放当前对象被哪个锁占用。
对象在内存中的布局:
对象头(markword/class pointer)+实例数据(instance data)+对齐(padding)
markword存储了很多和当前对象运行时状态有关的数据。(hashcode/锁状态标志/GC)
4Byte
synchronized通过javac转化成字节码指令(monitorenter/monitorexit)使线程同步。
monitor依赖操作系统的mutex lock来实现的。
public class TestSync {
private int num = 0;
public void test() {
for (int i = 0; i < 1000; i++) {
synchronized (this) {
System.out.println("thread:"+Thread.currentThread().getId()+", num:"+num++);
}
}
}
}
public class Main {
public static void main(String[] args) {
TestSync sync = new TestSync();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
sync.test();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
sync.test();
}
});
t1.start();
t2.start();
}
}
thread:21, num:599
thread:21, num:600
thread:22, num:601
thread:22, num:602
多线程的:wait、notify
无锁:非锁方式同步线程CAS。
偏向锁:不用CAS和mutex lock。对象头的markword,锁标志位(2bit)为01,是否偏向锁(1bit)为1,最前面的23bit位(线程ID)只有一个。假如多个线程ID,升级为轻量级锁。
轻量级锁:指向栈中锁记录的指针(30bit)+锁标志位(2bit)为00。
- 当一个线程获取对象的锁时,看到markword的锁标志位为00,那么就是轻量级锁。
- 这时,线程会在
虚拟机栈开辟Lock Record空间(存放markword副本+owner指针)。 - 线程通过CAS尝试获取锁,获得锁后:将markword复制给Lock Record,将owner指针指向该对象;将对象的markword的前30bit指针指向虚拟机栈的Lock Record。
- 完成线程和对象锁的绑定。
有别的线程想获得对象锁,就CAS(自旋,CPU空转)->适应性自旋(自旋时间不固定,上次自旋时间+锁状态)决定。
当自旋等待的线程自旋个数超过CPU核数的一半 或者自旋次数超过10次,就会升级为重量级锁,使用monitor堆线程控制,完全锁定资源。
自旋->线程等待空转,不调用重量锁挂起线程、锁->挂起线程,状态切换
“悲观锁”(Pessimistic Concurrency Control):对资源进行锁定,一个一个来。
“乐观锁”(Optimistic Concurrency Control):无锁的同步机制
同步代码块 vs 线程切换
for (int i = 0; i < 3; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
/*while(num < 1000) { // 各线程num值未同步
System.out.println("Thread name:" + Thread.currentThread().getName() + ":" + num++);
}*/
synchronized (Main.class) { // 互斥锁-悲观锁
while(num < 1000) {
System.out.println("Thread name:" + Thread.currentThread().getName() + ":" + num++);
}
}
}
});
t.start();
}
CAS
CAS(compare and swap),乐观锁,原子性,底层调用cmpxchg指令
old value:代表之前读到的资源对象的状态值;
new value:代表想要将资源对象的状态值更新后的值。
自旋:使其不断的重试CAS操作,通常会配置自旋次数来防止死循环。
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
static AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while(num.get() < 1000) {
System.out.println("Thread name:" + Thread.currentThread().getName() + ":" + num.incrementAndGet());
}
}
});
t.start();
}
}
}
Thread name:Thread-2:999
Thread name:Thread-2:1000
Thread name:Thread-0:906
Thread name:Thread-1:925
AQS(D)
AbstractQueuedSynchronizer
java.util.concurrent.locks
资源是否被占用的标志位(线程有独占模式、共享模式):
private volatile int state;
FIFO(队列-先进先出的双向链表)
private transient volatile Node head;
private transient volatile Node tail;
static final class Node {
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus; //等待状态
volatile Node prev; //前指针
volatile Node next; //后指针
volatile Thread thread; //线程对象
Node nextWaiter;
...
}
尝试获取锁(修改标记位),立即返回 ---> tryAcquire
protected boolean tryAcquire(int arg) { //用户对该方法重写
throw new UnsupportedOperationException();
}
获取锁(修改标记位),愿意进入队列等待,直到获取 ---> acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter():将当前线程封装后加入等待队列
tryRelease
release
ReentrantLock(D)
基于AQS,在并发编程中它可以实现公平锁和非公平锁来对共享资源进行同步,
同时,和synchronized—样,ReentrantLock支持可重入,
除此之外,ReentrantLock在调度上更灵活,支持更多丰富的功能。
public class ReentrantLock implements Lock, java.io.Serializable {...}
JUC(D)
ConcurrentHashMap(JDK1.7)
HashMap分布策略(JDK1.8):
- 数组的长度始终保持为2的次幂
- 将哈希值的高位参与运算
- 通过位与操作来等价取模操作
HashMap动态扩容(JDK1.8):
- 数组长度,因此数组长度length的二进制表示会在高位多出1bit
- length会参与与运算确定下标位置,
- 因此,扩容后的下标要么不变,要么移动的位置?
问题:
- 扩容出现环形链表异常
- 多线程put会造成脏数据读写
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {...}
ConcurrentHashMap核心分段锁:Segment数组(继承ReentrantLock) + HashEntry数组
线程池
ThreadPoolExecutor:int变量保存->线程池状态(3bit)+工作线程数(29bit)
JVM基础
参数调优
垃圾回收算法
堆内存的新老永
运行数据区
类加载器