多线程锁的os内核理解
os同步的方式
- 1.互斥量(mutex)
- 2.自旋锁(spinlock)
- 3.信号量
sysnchronized
1.关键字实现在interpreterRuntime.cpp中的moniterenter
2.synchronizer.cpp
- 底层使用的不是自旋锁
- jvm内部获取锁的时候也没有自旋
3.锁膨胀
mutex
操作系统级别同步原语
man pthread_mutex_init
-
初始化和销毁一个互斥量(一把锁)
-
引入pthread.h头文件
-
mutextest.c
-
sharei = 0;
mutex = PTHREAD_MUTEX_INITIALIZER(调用了上面man后面的方法)
-
pthread_t t1,t2,t3
int ret;
ret = pthread_create($t1,null,increase_num(void),null)
-
incease_num(void)
{
pthead_mutex_lock(&mutex)
tmp = sharei;
tmp = tmp +1;
share = tmp;
pthread_mutex_unlock(&mutex)
}
-
-
synchronized底层用的就是mutex,是一把互斥锁,是一把重量级锁,不是自旋锁
-
1.mutex实现的锁是互斥锁,java当中对于synchronized关键字如果是重量锁(数字10),它底层同步或者锁机制用的是mutex;如果不是10,与对象头相关,所以也可能是轻量锁。
2.什么时候变成重量级锁,是因为锁膨胀,是可逆的,当一个线程持有锁,当另一个也来获取锁时,此时锁会膨胀,变为重量级锁。
3.线上很多代码,很多时候没有并发,所有直接用synchronized是性能很差的,此时只是偏向锁,也就是说高并发情况下才会变成重量级锁,所以高并发情况下,尽量避免用锁,通过mq、copyonwrite、解决,对于超卖这种特殊情况则需要使用synchronized,
4.mutex为什么是重量锁?mutex互斥特点是拿不到锁就会sleep,调用sleep()会进入内核态,sleep是一种内核操作,因为发生了一次系统调用,jdk1.6之间是重量级锁,所以睡眠线程进入了内核态,当线程唤醒,它被唤醒,又从内核态切换为用户态。ReentrantLock中没有调用sleep,只用了cas,而cas不是内核操作,所以ReentrantLock是轻量级锁。
5.什么是内核态?线程的本质 ->jvm ->内核线程(一对一模型)
5a. 优点:jvm不需要做什么操作,通过各种native方法区实现了线程
5b.缺点 由于线程的底层需要内核线程来实现,所以java多线程切换时,需要频繁切换内核态和用户态,造成资源浪费
6.内核态?用户态?跟线程无关,跟cpu有关,需要看当前cpu是什么状态,执行自己的代码是用户态,执行系统代码,需要进行系统的内核升级,此时就变成了内核态,
6.a mmu mmap,虚拟地址映射,每一个核都有一个单独的mmu,电脑中的内存地址是虚拟地址,只是先分配一个虚拟地址,真实调用时才会去映射到真实的地址,这样cpu只有4g,所以也可以运行大于这个容量的程序。
用户程序 -》 d -> 内存mu -》cache ->寄存器
6.b 假设有4g,进程共享操作系统的内核,但是有自己的独立代码
6.c 上下文切换
1.进程之间的上下文切换(跨进程)
2.进程内部用户和内核切换
3.线程和线程之间的切换(进程内部跨线程)
4.线程内部用户态和内核态切换
-
user o = new user();{
i = 0;对象头里面的i值不同,锁的状态不同
}
-
synchronized(o)
-
自旋锁
-
代码
-
pthread_spinlock_t a_lock
pthread_spin_init(&a_lock);//初始化
pthread_create t1
pthread_create t2
pthread_create t3
pthread_spin_lock(&a_lock);//不会sleep,只会不断去获取锁
-
-
描述锁
- 自旋锁有两种层面
- 操作系统,os自旋
- 平台级别,reentrantLock.lock()(有个死循环一直tryAcquire()),
- park就是sleep,
- reentrantLock第一次拿锁失败,不会立即睡眠,不想立刻进入队列,不想睡眠,不想进入内核态,假设另一个线程释放了锁,所以再去拿锁,就可以拿到
- java层面没有直接调用spinLock的代码,因为是操作系统的空转,性能低,它的原理是去模仿spin的空转
- 自旋锁有两种层面