多线程高频考点(如何解决死锁,谈谈HashMap HashTable ConcurrentHashMap的区别)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


CountDownLatch

类似于一个跑步比赛,当比赛开始的时候,这个比赛啥时候结束,当最后一个选手撞线就结束
JUc提供了一个类来解决上述问题
使用的时候先设置一下有几个选手
每个选手撞线了,就调用一下countDown方法(计数器–,线程阻塞)
当撞线的次数达到了选手的个数,就任务比赛结束了(await被唤醒)

  public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(() -> {
                System.out.println("选手出发! " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("选手到达! " + Thread.currentThread().getName());
                //撞线
                countDownLatch.countDown();
            });
            t.start();
        }
        //await是进行阻塞等到,会等到所有选手都撞线之后,才解除阻塞
        countDownLatch.await();
        System.out.println("比赛结束");
    }

写时拷贝:copyonwriteArrayList一写多读不加锁保证线程安全
如何避免读到修改一数据:
在这里插入图片描述

旧方法修改不一定是原子的,可能读到修改了一半的数据,解决方法拷贝一份,在新的修改,引用指向新的顺序表,释放旧的内存

重点难点:

一、谈谈HashMap HashTable ConcurrentHashMap的区别

1.线程安全角度:

Hashmap不安全
Hashtable, CoucurrentHashtable安全

2.多线程下的优化:(CoucurrentHashMap好处)

1.锁粒度的控制:

HashTable 直接在方法上加synchroinzed,相当于是对this加锁,相当于是针对哈希表对象来加锁,一个哈希表,只有一个锁多个线程,无论这些线程都是如何操作的这个哈希表,都会产生锁冲突

CoucurrentHashMap每个哈希桶都有自己的锁,大大降低了锁冲突的概率,性能也就大大提高了
在这里插入图片描述

2.CoucurrentHashMap做了一个激进的操作:

只是个写操作加锁,读操作不加锁了
如果一个线程读,一个线程修改,也没有锁冲突
是否担心读的是修改一半的数据呢
不必担心,CoucurrentHashMap设计的时候保证读的是整个数据(要么是旧版本,要么是新版本,不会说读了修改一半的数据),另外广泛使用volatile保证内存可见性,读到数据是及时的

3.充分的利用到了cas特性

比如像维护个数,都是通过cas来实现,而不是加锁
包括还有些地方使用cas实现的轻量级锁来实现

总之,concurrentHashMap思路是能不加锁,就不加锁,尽一切可能来减低锁冲突的概率

4.ConcurrentHashMap对于扩容操作,进行了特殊的优化

化整为零(类似于拷贝)
HashTable的扩容方式是当put时候发现负载因子已经超过阈值,就触发扩容,申请一个更大数组,,然后把之前旧的数据给搬到新的数组上

很大的问题:
如果元素个数特别多,开销就很大,触发put这一操作可能会卡很久

CoucurrentHashMap在扩容的时候,就不再是直接一次性完成搬运了,而是搬运一点
扩容的时候,旧的和新的会同时存在一段时间,每次进行哈希表的操作,都会把旧的内存上的元素搬运到一部分到新的空间上,直到搬运完成
如果要查询元素旧的新的一起查
插入元素,直接往新的上插入
如果是删除元素,直接删了不用搬运了

分段锁:是旧版本的CouCurrentHashMap,是好几个链表公用同一个锁(java8开始就没了,锁冲突概率高,代码实现复杂)

5.key值

HashMap key允许为null
HashTable 和 CouCurrentHashMap key值不能为null

二、如何解决死锁

常见死锁场景

1.一个线程,一把锁,连续加锁俩次,如果不可重入,死锁

例子:
count++;

2.俩个线程,俩把锁

例子:1.钥匙锁车里,车钥匙锁家里
2.吃饺子:拿醋给酱油,拿酱油给醋
3.程序员修复一码通
死锁代码:

//演示死锁
public class demo34 {
    public static void main(String[] args) {
        Object locker1=new Object();
        Object locker2=new Object();
        Thread t1=new Thread(()->{
            System.out.println("t1尝试获取locker1");
            synchronized (locker1){
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println("t1尝试获取 locker2");
                synchronized (locker2){
                    System.out.println("t1 获取俩把锁成功");
                }
            }
        });
        Thread t2=new Thread(()->{
            System.out.println("t2 尝试获取locker1");
            synchronized (locker2){
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println("t2 尝试获取 locker2");
                synchronized (locker1){
                    System.out.println("t2获取俩把锁成功");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

产生死锁条件有双重循环,加锁顺序

3.多个线程多把锁:

哲学家进餐问题:

在这里插入图片描述
五个哲学家,五根筷子,
假设哲学家要么思考人生,啥事不做(阻塞)
要么吃面条,先拿左手筷子,再拿起右手筷子,吃一会就放下(线程执行)
由于线程调度是随机的,哲学家们,啥时候吃面,啥时候思考人生不确定
在这里插入图片描述

极端情况下:同时拿起左手筷子,就死锁了

解决方案:打破循环等待

约定:俩把锁必须先获取编号小端,后获取编号大的,不是先左后右,有效避免循环等待

银行家算法,统称分配

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值