1. volatile关键字的描述及作用

在 Java 里,volatile 是一种类型修饰符,主要用来修饰变量。它具备以下两方面的作用:

保证可见性

在多线程环境中,每个线程都有自己的工作内存,变量会被拷贝到工作内存中进行操作。当一个变量被声明为 volatile 时,意味着该变量会直接从主内存读取,并且每次对它的修改都会立即刷新到主内存。这样一来,其他线程就能马上看到该变量的最新值。

下面是一个简单示例:

class VolatileExample {
    // 声明为 volatile 的变量
    volatile boolean flag = false;

    public void writer() {
        // 修改 volatile 变量
        flag = true; 
    }

    public void reader() {
        while (!flag) {
            // 等待 flag 变为 true
        }
        System.out.println("Flag is now true");
    }
}

在上述代码中,flag 被声明为 volatile。当 writer 方法修改 flag 的值时,reader 方法能立刻看到这个变化。

禁止指令重排序

指令重排序是指编译器和处理器为了提高性能,会对指令进行重新排序。不过,volatile 关键字能禁止指令重排序,保证代码的执行顺序和编写顺序一致。

2. volatilesynchronized 关键字的区别

功能层面

  • volatile:主要保证变量的可见性和禁止指令重排序,但不能保证原子性。例如,对 volatile 变量进行自增操作(i++)就不是原子的,因为它包含读取、加 1 和写入三个操作。
  • synchronized:不仅能保证变量的可见性,还能保证代码块或方法在同一时刻只能被一个线程访问,也就是保证了原子性。

性能层面

  • volatile:使用成本较低,因为它不会引起线程的阻塞。它只是简单地保证对变量的读写操作是直接在主内存中进行的。
  • synchronized:使用成本较高,因为它涉及到线程的阻塞和唤醒,会带来上下文切换的开销。

使用场景层面

  • volatile:适用于一个变量被多个线程读取,而只有一个线程进行写操作的场景,或者是对变量的操作本身就是原子的情况。
  • synchronized:适用于多个线程对共享资源进行读写操作,需要保证操作的原子性和可见性的场景。

下面是一个对比两者的示例:

class Counter {
    // 使用 volatile 修饰的变量
    volatile int volatileCount = 0; 
    // 普通变量
    int synchronizedCount = 0; 

    public void incrementVolatile() {
        // 非原子操作
        volatileCount++; 
    }

    public synchronized void incrementSynchronized() {
        // 原子操作
        synchronizedCount++; 
    }
}

在这个示例中,incrementVolatile 方法对 volatileCount 的操作不是原子的,而 incrementSynchronized 方法对 synchronizedCount 的操作是原子的。