synchronized
是 Java 中的一个关键字,翻译成中文是同步的意思。
主要解决的是:多个线程之间访问资源的同步性。可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
使用方式
使用 synchronized
关键字时,需要在方法或代码块上加锁,以确保同一时间只有一个线程可以执行这段代码;
不可以。
因为构造方法本身就属于线程安全的,不存在同步的构造方法一说。
synchronized
同步语句块时
使用的是 monitorenter
和 monitorexit
指令,其中 monitorenter
指令指向同步代码块的开始位置,monitorexit
指令则指明同步代码块的结束位置。
synchronized
修饰方法时
没有 monitorenter
指令和 monitorexit
指令,取而代之的是 ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法。
volatile 关键字是 Java 中用于保证变量可见性和禁止指令重排序优化的一种机制。只能保证单个变量的原子性,不能保证复合操作的原子性。
volatile 关键字主要解决了两个问题:可见性问题和指令重排序问题。
可见性问题
在多线程环境下,每个线程都有自己的工作内存,用于存储线程需要使用的变量。当一个线程修改了某个变量的值时,这个变量的值并不会立即被其他线程看到,需要将该变量的值同步到主内存中,其他线程才能看到该变量的最新值。
而 volatile 关键字可以保证变量的修改对其他线程可见,从而避免了可见性问题。
指令重排序问题
在多线程环境下,为了提高程序性能,CPU 可能会对指令进行重排序,但指令重排序可能会导致程序出现意想不到的结果。例如,在一个双重检查锁定中,如果不使用 volatile 关键字,可能会导致另一个线程获取到未完全初始化的对象,从而出现问题。
而使用 volatile 关键字可以禁止指令重排序优化,保证了程序的正确性。
synchronized
关键字和 volatile
关键字是两个互补的存在,而不是对立的存在!
volatile
关键字是线程同步的轻量级实现,所以 volatile
性能肯定比 synchronized
关键字要好 。但是 volatile
关键字只能用于单个变量而 synchronized
关键字可以修饰方法以及代码块。volatile
关键字能保证数据的可见性,但不能保证数据的原子性。synchronized
关键字两者都能保证。volatile
关键字主要用于解决单个变量在多个线程之间的可见性,而 synchronized
关键字解决的是多线程之间的资源同步。先讲下 volatile 是怎么保证可见性的吧
volatile 可以确保对某个变量的更新对其他线程马上可见,一个变量被声明为 volatile 时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存 当其它线程读取该共享变量,会从主内存重新获取最新值,而不是使用当前线程的本地内存中的值。
例如,我们声明一个 volatile 变量 volatile int x = 0,线程 A 修改 x=1,修改完之后就会把新的值刷新回主内存,线程 B 读取 x 的时候,就会清空本地内存变量,然后再从主内存获取最新值。
那线程 B 是怎么知道 x 被修改了的呢?
在 Java 内存模型中,当一个线程修改了 volatile
变量的值,会将这个变量的新值刷新回主内存,并且通知其他线程,告诉它们这个变量的值已经发生了改变。当其他线程(比如线程B)需要读取这个 volatile
变量的值时,它们会从主内存中获取最新的值,而不是从本地内存中读取。
具体来说,当线程 A 修改了 volatile
变量 x
的值为 1,并将新的值刷新回主内存后,主内存会记录这个变化,并在内部维护一个变量版本号。其他线程(如线程 B)在读取 volatile
变量时,会从主内存中获取最新的值,并检查版本号,以确保它们读取的是最新的值。
Java 内存模型(JMM)是一种规范,是一种抽象的模型,用于定义在多线程环境下,线程之间如何与主内存和工作内存进行交互,以及如何保证线程之间的可见性、有序性和原子性。
JMM 主要关注以下几个概念:
总的来说,Java 内存模型是为了解决多线程环境下可能出现的可见性、有序性和原子性问题而设计的。它规定了线程之间如何与内存交互,以及如何保证线程安全。了解 JMM 对于编写并发安全的多线程程序至关重要,因为它帮助开发人员理解线程之间的交互规则,从而避免出现意外的并发问题。
为什么对 volatile 变量的读写操作具有原子性?
在 Java 中,对 volatile 变量的读写操作具有原子性,这意味着单个读取或写入 volatile 变量的操作在执行过程中不会被中断,也不会受到其他线程的干扰,从而确保了操作的完整性。
– 这种特性是由 Java 内存模型(JMM)所规定的。
x+1
为一次,x=x+1
为一次),虽然单个的读取和写入操作都是原子的,但是组合在一起时并不是原子的。在这种情况下,应该使用锁或其他同步机制来确保原子性。Java并发常见面试题总结(中) | JavaGuide(Java面试 + 学习指南)
聊聊Java关键字synchronized | Shark Chili
面渣逆袭 | Java并发编程