并发三大性质以及一些其他概念

概念

JMM模型上一篇博客
9d9d7def0c3e7815c920ff5b54d576d3.png

临界区:同一时刻只能有一个任务访问的代码区

互斥量:是一个可以处于两态之一的变量:解锁和加锁

信号量Semaphore:又称信号灯,用来在多线程环境下,用来保证两个或多个关键代码不被并发调用。在进入一个关键代码段全必须获取一个信号量,一旦关键代码完成,线程必须释放信号量。其他想要进入该关键代码段的线程必须等待第一个线程释放该信号量。
信号量和互斥量的区别
  • 互斥量用于线程的互斥,信号量用于线程的同步
  • 互斥量值只能为0/1,信号量值可以为非负整数。信号量可以实现多个同类线程的互斥和同步
  • 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,一个线程得到
    简单的信号量实现
public class Semaphore {
private boolean signal = false;   //使用signal可以避免信号丢失
public synchronized void take() {
    this.signal = true;
    this.notify();
}
public synchronized void release() throws InterruptedException{
    while(!this.signal) //使用while避免假唤醒
        wait();
    this.signal = false;
    }
}

使用场景

Semaphore semaphore = new Semaphore();
SendingThread sender = new SendingThread(semaphore);
ReceivingThread receiver = new ReceivingThread(semaphore);
receiver.start();
sender.start();

public class SendingThread {
    Semaphore semaphore = null;
    public SendingThread(Semaphore semaphore){
        this.semaphore = semaphore;
    }
    public void run(){
        while(true){
            //do something, then signal
            this.semaphore.take();
        }
    }
}

public class RecevingThread {
    Semaphore semaphore = null;
    public ReceivingThread(Semaphore semaphore){
        this.semaphore = semaphore;
    }
    public void run(){
        while(true){
        this.semaphore.release();
        //receive signal, then do something...
        }
    }
}

引用自https://www.jianshu.com/p/c71840db31d2

CAS:Compare and Swap

字面比较然后交换
java.util.concurrent包中借助CAS实现了区别于Sychronized同步锁的一种乐观锁
compare and swap,无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。
CAS的语义是我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改并告诉V的实际值为多少。
当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。拿当前对象的值和底层的值进行对比,直到对象的值和底层的值一样时才执行对应的操作,不一样就一直取最新的值。
CAS死循环内不断尝试修改目标值直到修改成功,如果竞争不激烈修改成功概率很高。竞争激烈的时候失败率很高,不断尝试会影响性能。
对于普通double、long类型,JVM允许64位读操作或写操作拆分为2个32位的操作

wait、sleep、notify、notifyAll

wait:使一个线程进入等待(阻塞状态),并且释放所持有的对象的锁
sleep:使一个正在运行的线程进入睡眠状态,是一个静态方法,调用此方法要处理InterruptException
notify:唤醒一个处于等待状态的线程,但是具体唤醒哪一个则不确定,完全取决于JVM
notifyAll:唤醒所有等待中的线程,该方法不是把对象的锁还给线程而是让他们竞争,只有获得锁的线程才能进入就绪状态

wait和sleep的区别
  • sleep来自Thread类,而wait来自Object类
  • 调用sleep不会释放对象锁,而调用wait会释放
  • sleep调用之后不会出让系统资源,而调用wait后让出系统资源让其他线程可以占用CPU
  • sleep需要指定一个时间,时间一到自动唤醒

并发三大性质

  • 原子性
  • 可见性
  • 有序性
    原子性:一个操作不可中断,要么全部执行成功,要么全部执行不成功。提供了互斥访问,同一时刻只能有一个线程来对它进行操作。
    可见性:当一个线程修改了主内存的共享变量后能够及时的被其他线程观察到
    有序性:synchronize语义要求线程在访问读写共享变量时只能”串行”执行,因此Synchronize具有有序性。
    为了性能优化,编译器和处理器会进行指令重排序,因此如果在本线程内观察,所有操作都是有序的;如果在一个线程观察另一个线程,所有的操作都是无序的
原子性-Atomic包
  • atomic包:核心就是CAS,使用的是Unsafe类用的compareAndSafeInt方法(CAS),其实就是将当前值和期望值(底层值)相同,才赋值,否则就一直循环。CAS缺点就是在一个死循环中尝试着修改值,竞争不激烈修改很高,当竞争高,效率下降。对于普通long,double类型,JVM允许64位读操作或写操作拆成2个32位的操作
  • JDK8增加了LongAdder类,在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base直接更新很好保障和AtomicLong一样,高并发通过分散提高了性能,缺点是统计时可能会有误差。(优先用)
  • compareAndSet常用于AtomicBoolean,希望某件事执行一次
  • AtomicReference和AtomicIntegerFieldUpdater原子性去更新某个类的实例的某一个字段(volatile,不能是static)
  • AtomicStampReference:解决的是CAS的ABA问题,加了个版本号来区别。
LongAdder的实现思想:热点数据分离。把atomicLong value分离为数组,每个线程访问时通过hash等算法映射到其中一个数组计数,最终的计数结果是数组的求和累加,热点数据value就被分离成多个单元,提高了并行度。高并发分散提高性能,但准确度会有偏差。
  • Synchronize:依赖JVM,作用对象的作用范围内,都是同一时刻只有一个线程操作的
  • Lock:依赖特殊的CPU指令,代码实现,ReentrankLock
Synchronized
  • 修饰代码块:大括号括起来的代码,作用于调用的对象
  • 修饰方法:整个方法,作用于调用的对象
  • 修饰静态方法:整个静态方法,作用于所用对象
  • 修饰类:括号括起来的部分,作用于所有对象
    前两个锁的是当前对象,当多个对象则互不影响。
    当前类是父类,子类继承父类的Synchronized方法是不带synchronized关键字的,必须自己加上
原子性对比
  • synchronized:不可中断所,适用竞争不激烈,可读性好
  • Lock:可中断锁,多样化同步,竞争激烈时能维持常态
  • Atomic:竞争激烈时能维持常态,比Lock性能好,只能同步一个值
可见性
导致共享变量在线程内不可见的原因
  1. 线程交叉执行
  2. 重排序
  3. 共享变量更新后的值没有及时在主内存刷新及工作空间
可见性-Synchronize
  1. 线程解锁前,必须将共享变量的最新值刷新到主内存中
  2. 线程加锁时,清空工作内存中共享变量的值,使用共享变量时需要到主内存读取最新的值
可见性-volatile
  1. 对volatile变量写操作时,会在写操作后加入一条store屏障命令,将本地内存中的共享变量值刷新到主内存
  2. 对volatile变量读操作时,会在读操作前加入一条load屏障操作,从主内存读取共享变量
    f4ff6f5e58a7acd0b52fd26344ca35d9.png
    在这里插入图片描述
    注:volatile执行count操作是不安全的,没有原子性。常用于状态标记量
    volatile使用条件
    对变量的写操作不依赖于当前值
    该变量没有包含在具有其他变量的不变式子中
    适用于状态标记量和双重检测(单例模式)
有序性 happens-before
  1. 程序顺序规则:一个线程的每个操作,happens-before于该线程的任意后续操作
  2. 监视器锁规则:对一个锁的解锁,happens-before于这个锁的加锁
  3. volatile规则:对一个volatile域的写,happens-before于后续任意对这个volatile域的读
  4. 传递性:如果A happens-before于B,B happens-before于C,那么A happens-before于C
  5. start()规则:如果线程A内启动线程B,那么ThreadB.start() happens-before于B内的任何操作
  6. join()规则:如果线程A执行线程B的join操作并成功返回,那么线程B内的所有操作都happens-before于返回操作
  7. 程序终端规则:对对象的interrupt操作happens-before于被中断代码检测到中断事件的发生
  8. 对象终结规则:一个对象初始化happen-before于finalize
Synchronized和volatile的区别
  1. 粒度不同,前者针对变量 ,后者锁对象和类
  2. syn阻塞,volatile线程不阻塞
  3. syn保证三大特性,volatile不保证原子性
  4. syn编译器优化,volatile不优化
    要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
    1. 对变量的写操作不依赖于当前值。
    2. 该变量没有包含在具有其他变量的不变式中。
Synchronized和Lock的区别

ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

ReentrantLock获取锁定与三种方式:

  1. a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
  2. tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false
  3. tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false
  4. lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
    synchronized:
    在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronized,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
    ReentrantLock:
    ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
    https://blog.csdn.net/u012403290/article/details/64910926?locationNum=11&fps=1
    在这里插入图片描述
顺便讲一下Synchronized
  1. 作用于方法时,锁住的是对象的实例(this);
  2. 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen(jdk1.8则是metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程;
  3. synchronized作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。

总结

原子性:CAS,Synchronize,Lock,互斥访问,Atomic
可见性:volatile,Synchronize
有序性:as-if-serial,happens-before

安全发布对象
  1. 在静态初始化函数中初始化一个对象引用
  2. 将对象的引用保存到volatile类型域或AtomicReference对象中
  3. 将对象的引用保存到某个正确构造对象的final类型域中
  4. 将对象的引用保存到一个锁保护的域中

参考:
https://www.jianshu.com/p/c5d5d768a126
https://www.jianshu.com/p/d52fea0d6ba5
https://blog.csdn.net/Jae_Wang/article/details/80344486

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值