synchronized关键字和volatile关键字比较
"synchronized"关键字和"volatile"关键字都与多线程编程相关,但在功能和使用上有一些不同之处。
-
功能:
- synchronized:它用于实现线程之间的互斥访问,确保在同一时间只有一个线程可以进入被synchronized修饰的代码块或方法。它可以保证线程安全性,防止多线程并发访问导致的数据不一致性问题。
- volatile:它用于保证被修饰的变量在线程之间的可见性,即当一个线程修改了被volatile修饰的变量的值时,其他线程能够立即看到最新的值。它主要用于控制变量在多线程环境下的读写顺序,防止出现数据不一致的问题。
-
使用场景:
- synchronized:适用于需要互斥访问共享资源的情况,例如多个线程对同一个对象进行读写操作时。
- volatile:适用于需要保证变量的可见性,但不涉及复合操作(例如自增、自减)的情况。
-
扩展性:
- synchronized:它是一种重量级的操作,涉及到线程的上下文切换和锁的获取与释放,可能会引起性能问题。此外,synchronized只能用于实例方法、静态方法或代码块上。
- volatile:它是一种轻量级的操作,不涉及锁的获取与释放,因此开销较小。但它只能修饰变量,无法修饰方法或代码块。
综上所述,synchronized关键字适用于实现线程之间的互斥访问,确保线程安全性,而volatile关键字适用于保证变量的可见性。在实际应用中,根据具体的需求和场景选择适合的关键字来保证多线程程序的正确性和性能。
以下列出一个demo来比较一下两者对flag字段的处理过程
1、synchronized
package com.backend.utils;
public class SynchronizedVsVolatileDemo {
//private static volatile boolean flag = false;
private static boolean flag = false;
public static void main(String[] args) {
// 使用synchronized关键字的示例
synchronizedExample();
}
private static void synchronizedExample() {
Thread thread1 = new Thread(() -> {
synchronized (SynchronizedVsVolatileDemo.class) {
System.out.println("Thread 1: Starting");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Setting flag to true");
flag = true;
System.out.println("Thread 1: Exiting");
}
});
Thread thread2 = new Thread(() -> {
synchronized (SynchronizedVsVolatileDemo.class) {
System.out.println("Thread 2: Starting");
while (!flag) {
// 等待flag为true
}
System.out.println("Thread 2: Flag is now true, exiting");
}
});
thread1.start();
thread2.start();
}
}
输出结果:
Thread 1: Starting
Thread 1: Setting flag to true
Thread 1: Exiting
Thread 2: Starting
Thread 2: Flag is now true, exiting
当且仅当synchronized走完后,thread2才去执行,并且能够判断flag已被thread1更改为true,跳出while循环,输出Thread 2: Flag is now true, exiting,由此可见synchronized既可以保证原子性又可以保证可见性。
2、volatile
demo如下
package com.backend.utils;
public class SynchronizedVsVolatileDemo {
//private static volatile boolean flag = false;
private static boolean flag = false;
public static void main(String[] args) {
// 使用volatile关键字的示例
volatileExample();
}
private static void volatileExample() {
Thread thread1 = new Thread(() -> {
System.out.println("Thread 1: Starting");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Setting flag to true");
flag = true;
System.out.println("Thread 1: Exiting");
});
Thread thread2 = new Thread(() -> {
System.out.println("Thread 2: Starting");
while (!flag) {
// 等待flag为true
}
System.out.println("Thread 2: Flag is now true, exiting");
});
thread1.start();
thread2.start();
}
}
注意此时的flag并未添加volatile关键字,我们启动一下试试看
返回
Thread 2: Starting
Thread 1: Starting
Thread 1: Setting flag to true
Thread 1: Exiting
并且程序还没结束,卡在了while死循环中,thread2并不知道flag已经变更为true,所以程序还没结束
若flag添加volatile关键字
private static volatile boolean flag = false;
运行后的返回
Thread 1: Starting
Thread 2: Starting
Thread 1: Setting flag to true
Thread 1: Exiting
Thread 2: Flag is now true, exiting
虽然两个线程并驾齐驱,但在thread1变更flag为true后,thread2也能知晓flag已经变更为true,跳出while循环,结束thread2.
由此可见,volatile关键字可以保证可见性,但无法保证原子性
volatile关键字是线程同步的轻量级实现,并且volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块,所以多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞