多线程下的线程安全

一. 线程安全之可见性

1. JVM运行时数据区

在这里插入图片描述

2. Java内存模型(Java Memory Model ,JMM) VS JVM运行时数据区

在这里插入图片描述

2.1 初看Java内存模型

在这里插入图片描述

2.2 多线程中的问题

  1. 所见非所得, 无法肉眼去检测程序的准确性
  2. 不同的运行平台有不同的表现
  3. 错误很难重现
    eg 问题:(t1总的线程会一直执行下去为什么呢?jit对这种频繁调用的代码做了优化 会先取demo1.flag 赋值给一个局部变量 然后去判断这个局部变量的值 后面又详细的图)
    在这里插入图片描述在这里插入图片描述

2.3. 内存缓存

在这里插入图片描述

2.4. CPU指令重排

在这里插入图片描述

2.5. JIT编译器(Just in time compiler)

脚本语言与编译语言的区别?
解释执行:即咱们说的脚本,在执行时,由语言的解释器将其一条条翻译成机器可识别的指令
编译执行:将我们写的程序,直接编译成机器可以识别的代码。
java是什么语言:解释 编译执行的混合体。
在这里插入图片描述
在这里插入图片描述

2.6. volatile关键字

在这里插入图片描述

public class VisibilityDemo {
    // 运行标志
//    public volatile boolean flag = true;
    /*
     *不带volatitle的时候thread1不会停止  jit 做了优化对于循环取值的时候
     * boolean aa=demo1.flag
     * if(aa){
     *  while(true){
     *      i++
     *  }
     * }
     */
    public boolean flag = true;

    public static void main(String[] args) throws InterruptedException { //思维逻辑上看
        VisibilityDemo demo1 = new VisibilityDemo();
        System.out.println("代码开始了");
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (demo1.flag) {
                    i++;
                }
                System.out.println(i);
            }
        });
        thread1.start();
        TimeUnit.SECONDS.sleep(2); //JMM 必要性
        // 设置is为false,使上面的线程结束while循环
        demo1.flag = false;
        System.out.println("被置为false了.");
    }
}

2.7.shared variables 定义

在这里插入图片描述

2.8. 线程间操作的定义

在这里插入图片描述

在这里插入图片描述

2.9. 对于同步的规则定义

在这里插入图片描述

对于监视器m的解锁与所有后续操作对于m的加锁同步
–即1.加锁解锁不能进行指令重排 2.加锁后的操作,对于解锁后的其他获取锁的线程内容可见
在这里插入图片描述
在这里插入图片描述

对于每个属性写入的默认值(0,false,null)与每个线程对其进行的操作同步
new 一个对象初始值会是乱码,然后是默认的0,false等,然后再赋值50,james等。
在这里插入图片描述

启动线程的操作与线程中的第一个操作同步
– 启动线程的第一个操作 即将线程的状态由NEW改为Runnable. 即只有t2线程状态变更为Runnble的时候才能执行t2线程的第一行代码
在这里插入图片描述

线程T2的最后操作与线程T1发现线程T2已经结束同步。(isAlive,join可以判断线程时否终止)
在这里插入图片描述

如果线程T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步
通过抛出InterruptedException异常,或者调用Thread.interrupted或Thread.isInterrupted.
在这里插入图片描述

2.10 Happens-before先行发生原则

在这里插入图片描述

2.11 final 在jvm中的处理–没看懂 但是感觉不重要此处进记录

在这里插入图片描述

2.12 word tearing 字节处理

在这里插入图片描述

2.13 double和long的特殊处理

在这里插入图片描述

二. 线程安全之原子操作

1. 概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. CAS (Compare and swap)

在这里插入图片描述

在这里插入图片描述

public class CounterUnsafe {
    volatile int i = 0; //cas 硬件  内存地址 --Long 232323523454235

    private static  Unsafe unsafe =null;//Unsafe类时final修饰的

    private static  long valueOffSet;// 初始话的时用来存储 变量i对应的内存地址

    static {
        try {
//            unsafe = Unsafe.getUnsafe(); **该方法禁止程序直接调用会抛出异常 , 对于属性静止调用的我们可以通过发射机制来获取值**
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);

            Field field1 = CounterUnsafe.class.getDeclaredField("i");
            valueOffSet = unsafe.objectFieldOffset(field1);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }


    public void add() {
        for(;;) {
            int current = unsafe.getIntVolatile(this, valueOffSet); //**性能 : 分析 解决原子性问题 方式有: 阻塞 或 自旋(cas)   分别损失的是谁? 自旋属于计算损失cpu 阻塞hunt住线程 损失内存(某种意义上也会损失cpu)**
            //**系统中 内存 和  cpu 哪个更容易出现异常或 100% ?cpu更容易出事 内存出问题大概率是程序的问题 ---更多情况下我们会选择损耗内存**
            if (unsafe.compareAndSwapInt(this, valueOffSet, current, current + 1)){ //要么成功,要么失败
                break;
            };
        }

    }
}

2.1 JUC包内的原子操作封装类

在这里插入图片描述

public class CounterAtomic {
    //volatile int i = 0;
    AtomicInteger i = new AtomicInteger(0); //封装了CAS机制.
    public void add() {
        i.incrementAndGet();
    }
}

2.3 CAS的三个问题

在这里插入图片描述

在这里插入图片描述ABA 问题是由于仅仅比较值的时候可能在并发情况下与预期不一致的结果,解决方案:添加版本号


import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

// 实现一个 栈(后进先出)
public class Stack {
  // top cas无锁修改
  AtomicReference<Node> top = new AtomicReference<Node>();

  public void push(Node node) { // 入栈
    Node oldTop;
    do {
      oldTop = top.get();
      node.next = oldTop;
    }
    while (!top.compareAndSet(oldTop, node)); // CAS 替换栈顶
  }


  // 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
  public Node pop(int time) {

    Node newTop;
    Node oldTop;
    do {
      oldTop = top.get();
      if (oldTop == null) {   //如果没有值,就返回null
        return null;
      }
      newTop = oldTop.next;
      if (time != 0) {    //模拟延时
        LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
      }
    }
    while (!top.compareAndSet(oldTop, newTop));     //将下一个节点设置为top
    return oldTop;      //将旧的Top作为值返回
  }
}



public class ConcurrentStack {
  // top cas无锁修改
  //AtomicReference<Node> top = new AtomicReference<Node>();
  AtomicStampedReference<Node> top =
      new AtomicStampedReference<>(null, 0);

  public void push(Node node) { // 入栈
    Node oldTop;
    int v;
    do {
      v = top.getStamp();
      oldTop = top.getReference();
      node.next = oldTop;
    }
    while (!top.compareAndSet(oldTop, node, v, v+1)); // CAS 替换栈顶
  }


  // 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
  public Node pop(int time) {

    Node newTop;
    Node oldTop;
    int v;

    do {
      v = top.getStamp();
      oldTop = top.getReference();
      if (oldTop == null) {   //如果没有值,就返回null
        return null;
      }
      newTop = oldTop.next;
      if (time != 0) {    //模拟延时
        LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
      }
    }
    while (!top.compareAndSet(oldTop, newTop, v, v+1));     //将下一个节点设置为top
    return oldTop;      //将旧的Top作为值返回
  }
}
// 存储在栈里面元素 -- 对象
public class Node {
  public final String value;
  public Node next;

  public Node(String value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return "value=" + value;
  }
}

// 实现一个 栈(后进先出)
public class Stack {
  // top cas无锁修改
  AtomicReference<Node> top = new AtomicReference<Node>();

  public void push(Node node) { // 入栈
    Node oldTop;
    do {
      oldTop = top.get();
      node.next = oldTop;
    }
    while (!top.compareAndSet(oldTop, node)); // CAS 替换栈顶
  }


  // 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
  public Node pop(int time) {

    Node newTop;
    Node oldTop;
    do {
      oldTop = top.get();
      if (oldTop == null) {   //如果没有值,就返回null
        return null;
      }
      newTop = oldTop.next;
      if (time != 0) {    //模拟延时
        LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
      }
    }
    while (!top.compareAndSet(oldTop, newTop));     //将下一个节点设置为top
    return oldTop;      //将旧的Top作为值返回
  }
}


public class Test {
  public static void main(String[] args) throws InterruptedException {
    Stack stack = new Stack();
    //ConcurrentStack stack = new ConcurrentStack();

    stack.push(new Node("B"));      //B入栈
    stack.push(new Node("A"));      //A入栈

    Thread thread1 = new Thread(() -> {
      Node node = stack.pop(800);
      System.out.println(Thread.currentThread().getName() +" "+ node.toString());

      System.out.println("done...");
    });
    thread1.start();

    Thread thread2 = new Thread(() -> {
      LockSupport.parkNanos(1000 * 1000 * 300L);

      Node nodeA = stack.pop(0);      //取出A
      System.out.println(Thread.currentThread().getName()  +" "+  nodeA.toString());

      Node nodeB = stack.pop(0);      //取出B,之后B处于游离状态
      System.out.println(Thread.currentThread().getName()  +" "+  nodeB.toString());

      stack.push(new Node("D"));      //D入栈
      stack.push(new Node("C"));      //C入栈
      stack.push(nodeA);                    //A入栈

      System.out.println("done...");
    });
    thread2.start();

    LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);


    System.out.println("开始遍历Stack:");
    Node node = null;
    while ((node = stack.pop(0))!=null){
      System.out.println(node.value);
    }
  }
}

2.4 线程安全的概念

可见性问题
原子性问题
在这里插入图片描述

2.5 共享资源

在这里插入图片描述

三. java锁相关

3.1 java中锁的概念

在这里插入图片描述

3.2 synchronized

1. 锁优化

在这里插入图片描述

在这里插入图片描述

2. 锁的原理

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3. 锁的升级过程

在这里插入图片描述

4. 对象头详解

在这里插入图片描述

https://zhuanlan.zhihu.com/p/138582291
下面这篇文章貌似讲的更为详细 可以参考
https://blog.csdn.net/tongdanping/article/details/79647337
这篇文章讲解了如何在cpu高的情况下找到对应的线程然后根据Jstack来查看问题
https://www.cnblogs.com/wuchanming/p/7766994.html
https://www.jianshu.com/p/9e02bed1387c

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值