高并发下常见问题

本贴最后更新于 1850 天前,其中的信息可能已经东海扬尘

并发中变量可见性问题

造成线程安全,变量可见性问题的原因

volatile 关键字的用途,使用场景

什么是并发中的变量可见性问题

  • 问题 1.变量分为哪几类?

    • 全局变量
    • 局部变量
    • 属性(静态的,非静态的)
    • 本地变量
    • 参数
  • 问题 2,如何在多个线程间共享数据?

    • 用全局变量:静态变量,或共享对象
  • 问题 3,一个变量在线程 1 中被改变值了,在线程 2 中能看到该变量的最新值吗?

什么是并发中的变量可见性问题

public class VisibilityDemo {

    //状态标识
    //private static volatile boolean flag = true;

    private static boolean flag = true;

    public static void main(String [] args){

        new Thread(new Runnable() {
            @Override
            public void run() {
                int i= 0;
                while (VisibilityDemo.flag){
//                    synchronized (this){
//                        i++;
//                    }
                   // i++;
                }
                System.out.println(i);
            }
        }).start();


        try{
            TimeUnit.SECONDS.sleep(2);
        }catch(InterruptedException e){
            e.printStackTrace();
        }


        VisibilityDemo.flag= false;
        System.out.println("设置为false了");
    }

}
  • 并发的线程能不能看到变量的最新值,这就是并发中的变量可见性问题

    • 为什么会不可见?
    • 怎么才能可见?
怎么才能可见?
  • 方式一: synchronized

  • 方式二: volatile

为什么可见了呢?

JAVA 内存模型

JAVA 内存模型及操作规范
  1. 共享变量必须存放在主内存中
  2. 线程有自己的工作内存,线程只可操作自己的工作内存;
  3. 线程要操作共享变量,需从主内存中读取到工作内存,改变值后需从工作内存同步到主内存中。

image

JAVA 内存模型会带来什么问题?
请思考,有变量 A,多线程并发对其累加会有什么问题?如三个线程并发操作变量 A,大家读取 A 时都读到 A=0,都对 A+1,再讲值同步回主内存。结果是多少?

如何解决线程安全问题?

  • 内存模型也产生了变量可见性问题。

如何让线程 2 使用 A 是看到最新值?

  • 线程 1 修改 A 后必须立马同步回主内存;
  • 线程 2 使用 A 前需要重新从主内存读取到工作内存。

疑问 1:使用前不会重新从主内存读取到工作内存吗?

疑问 2:修改后不会立马同步回主内存吗?

image

JAVA 内存模型同步交互协议,规定了 8 种原子操作:

  1. lock(锁定):将主内存中的变量锁定,为一个线程所独占
  2. unclock(解锁):将 lock 加的锁定解除,此时其它的线程可以有机会访问此变量
  3. read(读取):作用于主内存变量,将主内存中的变量值读取到工作内存当中
  4. load(载入):作用于工作内存变量,将 read 读取的值保持到工作内存中的变量副本中
  5. use(使用):作用于工作内存变量,将值传递给线程的代码执行引擎
  6. assign(赋值):作用于工作内存变量,将执行性引擎处理返回的值重新复制给变量副本
  7. store(存储):作用于工作内存变量,将变量副本的值传送到主内存中
  8. write(写入):作用于主内存变量,将 store 传送过来的值写入到主内存的共享变量中
JAVA 内存模型-同步交互协议,操作规范

如果要把一个变量从主内存复制到工作内存,那就要按顺序执行 read 和 load 操作,如果要把变量从工作内存同步回主内存,就要按顺序执行 store 和 write 操作。注意,Java 内存模型只要求上述两个操作必须按顺序执行,而没有保证是连续执行。也就是说 read 与 load 之间,sotre 与 write 之间是可插入其他指令,如对主内存中的变量 a,b 进行访问时,一种可能出现顺序是 read a,read b,load b,load a。除此之外,Java 内存模型还规定了在执行上述八种基本操作时必须满足如下规则:

  • 不允许 read 和 load,store 和 write 操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况出现。
  • 不允许一个线程丢弃它的最近的 assin 操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。

synchronized 怎么做到可见性

  • Synchronized 语义规范:

    • 进入同步块前,先清空工作内存中的共享变量,从主内存中重新加载。
    • 解锁前必须把修改的共享变量同步回主内存
  • Synchronized 是如何做到线程安全的?

    • 锁机制保护共享资源,只有获得锁的线程才可操作共享资源;
    • Synchronized 语义规范保证了修改共享资源后,会同步回主内存,就做到了线程安全

volatile 的使用场景

  • volatile 的使用范围

    • volatile 只可修饰成员变量(静态的,非静态的)。Why?
    • 多线程并发下,才需要使用它。
  • volatile 典型的应用场景

    • 只有一个修改者,多个使用者,要求保证可见性的场景
    • 转态标识,如示例中的介绍标识。
    • 数据定期发布,多个获取者。

volatile 与 synchronized 区别

  • volatile 本质是告诉 JVM 当前变量在寄存器中的值是不确定的,需要从主存中读取。synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其它线程被阻塞。

  • volatile 仅能使用在变量级别,synchronized 则可以使用在变量、方法。

  • volatile 仅能实现变量修改的可见性,而 synchronized 则可以保证变量修改的可见性和原子性。《Java 编程思想》上说,定义 long 或 double 时,如果使用 volatile 关键字(简单的赋值与返回操作),就会获得原子性。(常规状态下,这两个变量由于其长度,其操作不是原子的)

  • volatile 不会造成线程阻塞,synchronized 会造成线程阻塞。

  • 使用 volatile 而不是 synchronized 的唯一安全情况是类中只有一个可变的域。

  • 当一个域的值依赖于它之前的值时,volatile 就无法工作了,如 n=n+1,n++ 等。如果某个域的值受到其他域的值的限制,那么 volatile 也无法工作,如 Range 类的 lower 和 upper 边界,必须遵循 lower<=upper 的限制。

  • 使用 volatile 而不是 synchronized 的唯一安全的情况是类中只有一个可变的域。

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3168 引用 • 8207 回帖
  • 并发
    75 引用 • 73 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...