每天一个面试题:
- 悲观锁、乐观锁
- Hashtable和concurrentHashMap
- 总结
开始全新的学习,沉淀才会有产出,一步一脚印!
面试题系列搞起来,这个专栏并非单纯的八股文,我会在技术的基础上,Debug解析,还会做一些实例的实现,实现一些简单的Demo,或者用于我做过的项目中去;
代码会同步在我的gitee中去,觉得不错的同学记得一键三连求关注,感谢:
链接: CasAndSync
12.11 忌收纳
混乱是自由,令人畏惧的自由
悲观锁、乐观锁
- 悲观锁
- 核心代表:就是锁机制:Synchronized和Lock锁
- 核心思想:只有占有了锁,才可去操作资源,否则就阻塞等待
- 过程:就是 运行-阻塞-唤醒 ,涉及到了上下文切换,如果频繁切换,对性能影响大
- 细节:实际上,线程在获取锁时,如果锁已经被占用,都会做几次重试操作,减少阻塞机会
- 乐观锁
- 核心代表:AtomicInteger,通过CAS保证原子性
- 核心思想:无需加锁,每次只有一个线程可以成功修改共享变量,其它失败的线程不需要停止,不断重试直至成功;
- 过程:不会阻塞,一直运行
- 细节:需要多核CPU支持吗,线程数不超过CPU数
public class TsetCasAndSync {
//为了实现原子性,U的操作都是原子的
static final Unsafe U = Unsafe.getUnsafe();
//表示偏移量:对于Account类和balance属性
static final long BALANCE = U.objectFieldOffset(Account.class, "balance");
static class Account{
//volatile 保证可见性
volatile int balance = 10;
}
上面是为了保证操作这个资源是可以访问其序列号/偏移量的
public static void main(String[] args) {
Account account = new Account();
while(true){
int o = account.balance;
int n = o+5;
//调用
if (U.compareAndSetInt(account, BALANCE,o,n)){
break;
}
}
while(true){
int o = account.balance;
int n = o-5;
//调用
if (U.compareAndSetInt(account, BALANCE,o,n)){
break;
}
}
}
}
这个针对了当前情况(U.compareAndSetInt(account, BALANCE,o,n)
,
o:当前变量
n:操作之后的变量
account:我们操作的资源
如果我们线程A操作account,到 if (U.compareAndSetInt(account, BALANCE,o,n)){ break; }
,如果balance被修改,那么当前线程会对比 account和o是否相同,不同就重新在来;
public class TsetCasAndSync {
public static void sync(Account account){
new Thread(()->{
synchronized (account) {
int old = account.balance;
int n = old - 5;
account.balance = n;
}
}).start();
new Thread(()->{
synchronized (account){
int old = account.balance;
int n = old + 5;
account.balance = n;
}
}).start();
}
static class Account{
volatile int balance = 10;
}
public static void main(String[] args) {
Account account = new Account();
sync(account );
}
}
锁机制,占用无法被修改
Hashtable和concurrentHashMap
- 都是线程安全的Map集合
- Hashtable并发度低,全局使用一把锁,同一时刻,只有一个线程可以操控它
- 1.8之前ConcurrentHashMap使用了Segment+数组+链表的结构,每个Segment对应一把锁,支持多线程操作
- 1.8之后ConcurrentHashMap将数组的每个头结点作为锁,如果多个线程访问的头结点不同,就不会冲突