多线程——进阶

目录

常见的锁策略

死锁

CAS

Synchronized 原理 

Callable 接口

ReentrantLock

信号量 Semaphore

CountDownLatch

在多线程环境下使用哈希表可以使用


常见的锁策略:

锁策略:锁的实现者预测后续锁冲突的概率大小,根据这个概率来决定接下来该怎么做

锁冲突:即锁竞争,两个线程针对一个对象加锁,产生阻塞等待

一、乐观锁和悲观锁

乐观锁:预测接下来冲突概率不大

悲观锁:预测接下来冲突比较大

二、轻量级锁和重量级锁

轻量级锁:加锁解锁,过程更高效、更快

重量级锁:加锁解锁,过程更慢、更低效

三、自旋锁和挂起等待锁

自旋锁是轻量级锁的一种典型表现,一直等到有资源为止,消耗cpu,但是一旦锁被释放,就能第一时间拿到锁,速度很快。

挂起等待锁是重量级锁的一种典型表现,不会忙等,但是不能第一时间拿到锁

四、互斥锁和读写锁

互斥锁:单纯的加锁

读写锁:把读和写两种加锁区分开

五、可重入锁和不可重入锁

可重入锁:在一个线程中,对一个锁加锁两次,不死锁

不可重入锁:在一个线程中,对一个锁加锁两次,死锁。eg;把车钥匙锁家里,把家钥匙锁车里

六、公平锁和非公平锁

公平锁:遵循先来后到

非公平锁:不遵循先来后到

eg:synchronized既是悲观锁也是乐观锁,既是轻量级锁也是 重量级锁,是互斥锁,是可重入锁,非公平锁(加锁的时候会判断一下,当前申请锁的线程是不是锁的拥有者,是就放行。eg:男朋友知道是你的小号会答应你的表白)synchronized会根据当前锁竞争的激烈程度适应。

死锁:

1.一个线程,一把锁,可重入锁没事,不可重入锁出现死锁  

2.两个线程,两把锁,是重入锁也会死锁

死锁的必要条件:

1.互斥使用:一个线程拿到一把锁之后,另一个线程不能使用。

2.不可抢占:一个线程拿到锁,只能自己自动释放,不能是被其他线程强行占有

3.请求和保持:“吃着碗里的,看着锅里的”

4.循环等待:车钥匙锁家里了,家钥匙锁车里了

解决方案:先获取小的,再获取大的

CAS:

定义:比较并交换

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

1. 比较 A 与 V 是否相等。(比较)

2. 如果比较相等,将 B 写入 V。(交换)

3. 返回操作是否成功。

!!CAS操作是一条cpu指令,是原子的

应用:

1.Atomlnteger类

代码可以展开详细写:

伪代码:     
   class AtomicInteger{
        private int value;
        public int getAndIncrement(){
            int oldvalue=value;
            while (CAS(value,oldvalue,oldvalue+1)!=true){
                oldvalue=value;
            }
            return oldvalue;
        }
    }
//如果发现value和oldvalue值相同,就让oldvalue+1,相当于++,然后CAS返回true,
//反之,value和oldvalue值不相同,返回false继续循环 


//刚把value的值赋给oldvalue,立马判断是否相等不会多此一举,因为有可能两个线程同时
//调用这个方法,所以这么判断就是CAS在确认value有没有变过

 2.实现自旋锁

CAS的ABA问题

使用CAS需要先判断值是否发生改变,但是有些值最后结果和原来是一样的,但是中途可能变过无数次。(这就好比, 我们买一个手机, 无法判定这个手机是刚出厂的新手机, 还是别人用旧了, 又翻新过的手 机.)

解决方法:

给要修改的值, 引入版本号.

在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期. CAS 操作在读取旧值的同时, 也要读取版本号(只能递增).

真正修改的时候, 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.

如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了). 

Synchronized 原理 

加锁升级过程:

 偏向锁:线程针对锁做个标记,如果没有遇到别的线程跟我竞争,那就不用真加锁,如果有竞争,立即升级为轻量级锁,别的线程要阻塞等待。

!!升级为重量级锁即在内核进行阻塞等待,意味着暂时放弃cpu,由内核进行后续调度

Callable 接口

定义:类似于runnable,只是描述一个任务,但是runnable没有返回值,callable有返回值

应用:创建一个线程,计算1+2+3+...+1000

    public class demo20 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建一个任务
        Callable<Integer> callable=new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum=0;
                for (int i = 1; i < 1000; i++) {
                    sum=sum+i;
                }
                return sum;
            }
        };
        //用FutureTask包装,FutureTask相当于取餐小票
        FutureTask<Integer> futureTask=new FutureTask<>(callable);
        //要找个对象来完成任务
        Thread t=new Thread(futureTask);
        t.start();
        //取结果
        System.out.println(futureTask.get());
    }
}

!!get会阻塞等待call方法执行完才执行

ReentrantLock

定义:可重入互斥锁

用法:lock(): 加锁, 如果获取不到锁就死等.

trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁.

unlock(): 解锁(写代码时容易遗漏)

ReentrantLock 和 synchronized 的区别:

1.synchronized 是一个关键字, 是 JVM 内部实现的(大概率是基于 C++ 实现).

2.ReentrantLock 是标准 库的一个类, 在 JVM 外实现的(基于 Java 实现). synchronized 使用时不需要手动释放锁.

3.ReentrantLock 使用时需要手动释放. 使用起来更灵活, 但是也容易遗漏 unlock. synchronized 在申请锁失败时, 会死等.

4.ReentrantLock 可以通过 trylock 的方式等待一段时间就 放弃. synchronized 是非公平锁,ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启 公平锁模式.

5.更强大的唤醒机制. synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一 个随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指 定的线程.

如何选择使用哪个锁?

锁竞争不激烈的时候, 使用 synchronized, 效率更高, 自动释放更方便.

锁竞争激烈的时候, 使用 ReentrantLock, 搭配 trylock 更灵活控制加锁的行为, 而不是死等.

如果需要使用公平锁, 使用 ReentrantLock.

信号量 Semaphore

定义:用于表示可利用资源数量,相当于计数器

CountDownLatch

同时等待多个任务执行结束(好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。)

在多线程环境下使用哈希表可以使用:

Hashtable(线程安全的,可以用synchronized加锁)

ConcurrentHashMap(更推荐)

Hashtable和ConcurrentHashMap的区别:

1.加锁力度(触发锁冲突的频率)的不同

Hashtable是对整个哈希表加锁,任何增删改查都会触发加锁操作

而ConcurrentHashMap是对一个链表的加锁,每次操作都是针对对应链表进行加锁,其他链表不会有锁冲突

ConcurrentHashMap更充分利用了CAS机制

ConcurrentHashMap优化了扩容策略,并不会一次性把所有元素搬过去,而是一点一点搬

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发呆的百香果子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值