多线程进阶

多线程进阶

一。锁策略

1.乐观锁与悲观锁:

这是锁的一种特性, “一类锁”不是具体的锁

乐观锁和悲观锁是对后续锁冲突是否频繁给出的预测

乐观锁:后续锁冲突的概率不大,可以少做一些工作

悲观锁:后续冲突的概率比较大,可能要多做一些工作

2.重量级锁与轻量级锁:

轻量级锁的开销比较小

重量级锁的开销比较大

当然也可以与上面的乐观悲观锁相联系,乐观锁其实就是一种轻量级锁;悲观锁就是一种重量级锁

这两个的区别就是,一个是预测锁冲突概率,一个是预测实际消耗的开销

3.挂起等待锁和自旋锁

自旋锁就属于一种轻量级锁的典型实现,往往是在纯用户态实现的,比如使用一个while循环,不停的检查当前锁是否被释放,如果没有被释放就继续循环,释放了就获得到锁,从而结束循环

挂起等待锁就是一种重量级锁,它的开销是很大的,要借助系统api来实现,一旦有锁竞争,就会在内核中触发一系列的动作(比如说让这个线程进入阻塞状态(阻塞的开销是很大的),暂时不参与系统CPU的调度)

4.读写锁:

读加锁:读的时候能读,但是不能写

写加锁:写的时候不能读,也不能写

5.非公平锁和公平锁:

背景:假设有多个线程,当第一次这些线程进行锁竞争的时候,其中一个线程拿到了锁的使用权,但是当它释放锁的时候,这时哪个线程会得到锁呢?

这就是会涉及到公平锁和非公平锁

公平锁:按照先来后到的顺序进行获取锁

非公平锁:按照随机的顺序进行获取锁

二。CSA

CAS的全称:compare and swap, 进行比较和交换的是内存和寄存器

假设现在有一个内存M,和两个寄存器A,B

CAS(M,A,B)

这时如果M和A的值相同,那么M和B的值进行交换,同时整个操作返回true

如果M和A的值不同,那么无事发生,返回false

交换的本质是为了把B赋值给M,寄存器B中的值是什么,其实没有必要关心

CAS其实是一个CPU指令,也就是说一个CPU指令,就可以完成上述交换的逻辑。单个CPU指令是原子的,就可以用CAS完成一系列操作,进一步替代加锁

优点:保证线程安全,避免阻塞

缺点:代码不好理解且只适用与一些特定的场景,不如synchronized灵活

CAS是可以实现原子类的,比如以int++为例,int++不是原子的

AtomicInteger是基于CAS的方式对int进行了封装,此时进行++就是原子的了,因此可以说明CAS是解决线程安全的另一种写法

import java.util.concurrent.atomic.AtomicInteger;

public class Demo1 {
    public static AtomicInteger count=new AtomicInteger(0);//重点!!!!
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
                for(int i=0;i<50000;i++){
                    count.getAndIncrement();//就是count++的意思
                    //count.getAndDecrement();//就是count--的意思
                }
        });
        Thread t2=new Thread(()->{
                for(int i=0;i<50000;i++){
                    count.getAndIncrement();
                }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count.get());
    }
}

原来的写法是public static int a=0;

现在的写法是 public static AtomicInteger a=new AtomicInteger( ); 这个括号里的值就是对a这个变量初始化对值

这个方法多在多线程计数时使用

当然CAS也是存在问题的:ABA问题

CAS进行操作的关键是“值”没有发生变化来作为“没有其他线程穿插执行的依据”

但是这种判断是存在问题的,有可能是这种情况:A->B->A,此时看起来没有任何问题,实际上其他线程已经进行了穿插,只不过最后又改回了原来的值了,ABA就算出现了,一般不会出现bug,就相当于买手机买到了一个二手翻新机

ABA问题出现问题的情况(非常巧合极端才会出现问题):

例子:假设一个人去银行取钱,这个人银行卡里有1000元,当他摁了取500一次,这时机器没有响应,然后他又按了一次,且正巧这时有人给他转了500元
在这里插入图片描述

通过上图可以看出,最后得到的结果是500元,这就是典型的ABA问题,在t2线程进行完把value改成了B(500元),再经过线程t3把value改回了A(1000元),这时再经历t1线程value又会被减500

因此可以发现:有增有减就回出现ABA问题,只增或只减就不会出现ABA问题

解决方法:引入一个额外的变量(版本号),约定每次修改余额的时候,都要让版本号自增,此时再使用CAS就不会判定余额了,而是判定版本号了,看版本号是否发生了变化

三。synchronized原理:

重要机制;锁升级;锁消除;锁粗化

锁升级:无锁->偏向锁->自旋锁->重量级锁

偏向锁:不是真正的加锁,只是做了一个标记,偏向锁的核心思想就是“懒汉模式”;能不加锁就不加锁,加锁就意味着开销。原理就是:如果没人来竞争,就不加锁;如果有人来加锁,就最先或得到锁

锁消除:也是一种编译器优化的手段,编译器会自动针对你写的加锁进行判断,如果编译器认为这个地方不需要加锁,则编译器会自动在这里把锁优化掉。

例如:在javaSE阶段学字符串的时候有StringBuilder和StringBuffer,他们之间的区别之一就是StringBuilder不带有synchronized,而StringBuffer带有synchronized,如果这时在单线程用StringBuffer,那么编译器会自动优化掉锁

编译器只会在自己最有把握的时候,才会进行锁消除的操作(触发的概率不会很高)

偏向锁与锁消除的区别

偏向锁是运行时的事情,运行时多线程的调度情况不同,这个锁可能有人竞争,也有可能没有;锁消除是编译过程中发生的

锁粗化:

锁的粒度:synchronized里,代码越多,锁的粒度越粗,代码越少,锁的粒度越细;

粒度细的时候能够并发执行的逻辑越多,更有利于充分利用多核CPU资源

但是如果颗粒度细的锁反复多次进行加锁解锁的操作,实际效果可能会不如粒度粗的锁

四。collable接口:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    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=0;i<1000;i++){
                    sum+=i;
                }
                return sum;
            }
        };
      //把任务放到进程中进行执行
        FutureTask<Integer> futureTask=new FutureTask<>(callable);
        Thread t=new Thread(futureTask);
        t.start();
        //此处get就能获取到callable里的返回结果
        //由于是并发编程,执行到get的时候,t线程可能还没有执行完,没执行完的化,get就会阻塞
        System.out.println(futureTask.get());
    }
}

这里的futureTask就相当于买奶茶的的时候,给你开的一张支票,等一会取的时候需要提供发票才能取奶茶

五。信号量(semaphone)

信号量就是一个计数器,描述了“可用资源”的个数。

每次申请一个资源的时候,计数器-1;称为“P操作”(英语中这里指的是acquire)

每次释放一个资源的时候,计数器+1;称为“V操作”(英语中这里指的是release)

这里的+1和-1都是原子的

信号量假设初始情况数值是10,每次进行P操作,数值就-1.当已经进行了10次操作之后,数值就变成了0;如果继续进行P操作,这时就会进入阻塞等待的状态(这里的阻塞等待有一种锁的感觉)

其实锁本质上是一种特殊的信号量,锁就是可用资源为1的信号量,加锁操作P操作,1->0;解锁操作V操作,0->1;因此也可称锁为二院信号量

开发中,如果遇到需要申请资源的情况,就可以使用信号量进行实现

import java.util.*;
import java.util.concurrent.Semaphore;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(4);
        semaphore.acquire();
        System.out.println("P操作");
        semaphore.acquire();
        System.out.println("P操作");
        semaphore.acquire();
        System.out.println("P操作");
        semaphore.acquire();
        System.out.println("P操作");
        semaphore.release();
        System.out.println("V操作");
        semaphore.acquire();
        System.out.println("P操作");
    }
}

此代码申请了4个资源,可以看到前4次都在申请资源,当在第5次都时候释放了一个资源,此时再进行申请资源是可以正常申请的,但是如果再在后面申请一次资源,这时会进入阻塞等待的状态

六。CountDownLatch

这个东西主要适用于多个线程来完成一系列任务的时候,用来衡量任务的进度是否完成,比如要把一个大任务,分成多个小任务,让这些任务并发的执行

就可以使用countDownLatch来判定说这些任务是否都完成了

CountDownLatch主要有两个方法:

1.await调用的时候就会进入阻塞,就会等待其他线程完成任务,索引线程都完成任务之后,此时这个await才会返回,才会继续往后走

2.countDown,告诉CountDownLatch,当前这个子任务完成了

import java.util.*;
import java.util.concurrent.CountDownLatch;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch=new CountDownLatch(10);
        for(int i=0;i<10;i++){
            int id=i;
            Thread t=new Thread(()->{
                System.out.println("thread"+id);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //通知当前任务完成了
                countDownLatch.countDown();
            });
            t.start();
        }
        countDownLatch.await();
        System.out.println("所有任务都完成了");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值