概念
总结一波多线程学习中遇见的术语
不可继承
Synchronize定义的方法被子类覆盖后并不是同步的,也就是不可继承性
虚假唤醒
由于wait方法调用后,如果被唤醒就会直接执行wait后面的代码,如果此时wait在一个if判断body中,将不会再次执行判断,所以要用while替代if操作
锁对象
如果锁的是成员方法,锁对象是this,也就是调用方法的对象,如果是静态方法,则锁对象是类
可重入锁
Synchronize和Lock都是可重入锁,也就是能自由进出锁和内层锁,递归进入,一个线程可以重复获得同一个锁
锁重入
在已经获得锁的同步方法或同步代码块内部可以调用锁定对象的其他同步方法,synchronize重入会在新增一条Lock Record,但不交换锁对象的MarkWord数据。
死锁
著名问题:哲学家就餐问题
操作系统层面分析产生死锁的条件
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
一个线程持有一把锁同时想获取另一个锁,对另一个线程同理,此时就会死锁。可以用jps、jstack工具检测到死锁
如果线程获取所得顺序做出特定的改变就能解决死锁问题。但可能会导致饥饿问题。
活锁
两个线程互相改变对方的结束条件,两个线程都一直无法结束。此处和synchronize没有关系,此处的锁代表的是广义的锁。两个线程的执行交错开就能解决活锁。
class liveLock{
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
while (count > 0){
Thread.sleep(3000);
count--;
}
}, "t1").start();
new Thread(() -> {
while (count < 30){
Thread.sleep();
count++;
}
}, "t2").start();
}
}
饥饿:
某个线程一直得不到执行机会
锁消除
由JIT即时编译器优化
sb这个引用只会在add中引用,不会被其他线程引用。所以JVM自动消除sb对象内部的锁
public void add(){
StringBuffer sb = new StringBuffer();
sb.append(str1).append(str2);
}
锁粗化
如果一连串操作都是对同一对象加锁,JVM会把加锁的范围粗化到一连串操作的外部。比如while循环中对StringBuffer进行append操作100次,此时while部分代码会被加一次锁
锁细分
一个类中有不同的业务实现,且之间数据不互相影响,这时为了提高并发度可以设置这个类的多把锁。业务不同的代码选择属于自己业务的那一把锁。比如Room类可以有睡觉和学习两个功能,那就实例化studyRoom和sleepRoom两把锁。睡觉的同步代码块选择sleepRoom为锁对象,学习的同步代码块选择studyRoom作为锁对象。
坏处就是一个线程可能就要获取多把锁,会导致死锁。比如A线程获得studyRoom锁后,在同步代码块里又想获得sleepRoom,而线程B恰好与之相反,此时就进入了死锁状态。
线程经验公式
线程数 = 核数 * 期望cpu利用率 * 总时间(cpu计算时间 + 等待时间) / cpu计算时间