多线程的宏观和微观视角

首先咱们在作并发编程的的时候会考虑到原子性丶可见性和有序性,在宏观上会考虑到安全性丶活跃性和性能;git

微观视角

  • 可见性: 一个线程对共享变量的修改,另一个线程可以马上感知到,咱们称为可见性;
  • 原子性: 一个或者多个操做在 CPU 执行的过程当中不被中断的特性称为原子性;
  • 有序性: 就是咱们代码的执行顺序,依赖等。(指令重排致使顺序被打乱);

  线程工做内存: 是指 Cpu 的 '寄存器''高速缓存',线程的 工做内存/本地内存 是指cpu的寄存器和高速缓存的抽象描述,数据读取顺序优先级 是:寄存器->高速缓存->内存github


宏观视角

  • 安全性: 安全性我认为实际上是包含了原子性丶可见性和有序性的,是一个总的概念,在程序开发的时候首先要注重安全性,会在下面详细解释这三点;
  • 活跃性 活跃性告诉咱们的是要避免死锁,饥饿和活锁; --- 死锁:这个都不陌生,线程A持有1锁,等待获取2锁,线程B持有2锁,等待获取1锁,这就是个典型的死锁,就就是阻塞了。 --- 饥饿:饥饿当多线程获取锁都得时候,总有线程没有机会获取到锁,出现饥饿的三中状况:1-高优先级的线程吞噬了低优先级线程的CPU使用权 2-线程被一直阻塞(好比Thread.Sleep) 3-等待线程永远不被唤醒,也能够理解为锁的优先级,咱们经常使用的synchronized就是非公平锁,例如线程A,B,C按顺序获取锁1,首先是A获取到了锁,执行完临界区代码释放了锁,这是线程D来了直接获取到了锁,这就是非公平锁;ReentrantLock()默认是非公平锁,能够在ReentractLock(true)建立公平锁; --- 活锁:在生活中A和B同时进入左手门,为了避免发生碰撞,A和B互相礼让同时进入了右手门,为了避免发生碰撞又进入了左手们会一直循环下去,实例代码找到适用的场景在增长
  • 性能 1:延迟: 延迟就是指一个请求调用到返回所使用的时间,时间越短,程序的处理的就越快,性能也就会高; 2:吞吐量: 吞吐量就是值在单位时间内(秒)处理的请求数量,吞吐量越大,程序处理的请求就越多,性能也越好;

可见性:线程工做空间致使可见性问题

  例如:线程A在主存中年将变量age=0拉去到本身的工做内存中,而后作了age = 5,固然这个操做是在cpu的寄存器中进行的,而后写会高速缓存中,这时线程A的高速缓存还未执行同步主内存的操做,线程B又将age=0从主存拉取到了线程B的工做内存中,致使A线程已经更新可是B线程看不到的可见性问题;编程

原子性:线程切换致使原子性问题 ++count

  例如:当线程A从主内存中将共享变量Count加载到线程A的工做内存后,发生了线程切换,这个时候线程B也将共享变量Count从主内存加载到了线程B的工做内存,这时线程A和B的工做内存中count都是0,线程B执行了Count = Count + 1,而后写回到主内存,这时候线程切换完成,回到了线程A再次执行 Count = Count + 1,再将线程A工做内存计算过的count写回主内存,如今咱们获得的主内存呢中Count值是1而不是2。缓存

有序性:指令重排致使有序性问题;

在这里讲一个例子,就是获取单例双重检查锁(double-checked locking)判断:安全

/**
 * @Auther: lantao
 * @Date: 2019-03-28 14:32
 * @Company: 随行付支付有限公司
 * @maill: lan_tao@suixingpay.com
 * @Description: TODO
 */
public class Test1 {
    
    private DoMain doMain;
    
    public DoMain getDoMain(){
        if(doMain == null){
            synchronized (this.getClass()){
                if(doMain == null){
                    doMain = new DoMain("");
                }
                return doMain;
            }
        }else{
            return doMain;
        }
    }
}

复制代码

  在上边的代码中在synchronized内和外都有一个if判断,判断doMain是否为null操做,有不少人对synchronized中的if null判断不理解,其实能够这样想,线程A和线程B都执行到了synchronized这里进行竞争锁,结果A获得锁,判断if null,结果还未实例化,继续进行实例化,而后return对象并释放锁,这时线程B获取到了锁进入if null判断,发现doMain已经被线程A实例化过了,直接返回实例便可,第二个if null的做用就在这里;bash

看上去上边的代码是完美的,可是new的操做上咱们理解是:多线程

  • 建立内存M
  • 在内存M上初始化doMain对象
  • 将内存M的地址指向变量doMain

可是实际上优化后(指令重排)的执行路径多是这样的:并发

  • 建立内存M
  • 将内存M的地址指向变量doMain
  • 将内存M的地址指向变量doMain

%E6%9C%89%E5%BA%8F%E6%80%A7%E9%97%AE%E9%A2%98.png

博客地址:lantaoblog.site性能

相关文章
相关标签/搜索