Java内存模型中的volatile和synchronized关键字

2023-08-22 18:44:00 浏览数 (1)

volatile关键字:

  • volatile关键字有两个作用:保证可见性和禁止指令重排序。
  • 保证可见性:当一个变量被volatile修饰时,它会被保证对所有线程的可见性。也就是说,当一个线程修改了这个变量的值,其他线程可以立即看到修改后的值,而不是使用缓存中的旧值。
  • 禁止指令重排序:当代码执行时,JVM为了优化,可能会对指令进行重排序。而有时候,这种重排序可能会导致程序出现错误。当一个变量被volatile修饰后,JVM会禁止对其进行指令重排序,从而保证程序的正确性。
  • 举例:假设有两个线程,一个线程负责写入变量,另一个线程负责读取变量。如果没有使用volatile关键字修饰变量,那么读取线程可能会一直读取缓存中的旧值,而写入线程可能会将新值一直保存在CPU的寄存器中,不会及时刷回内存。但是,如果使用volatile关键字修饰变量,那么写入线程修改变量的值后,会立即刷回到内存,而读取线程读取变量时,会从内存中获取最新的值,从而保证了可见性。

synchronized关键字:

  • synchronized关键字用于实现多线程之间的同步。通过加锁和释放锁的机制,确保在同一时间只有一个线程可以访问被Synchronized修饰的方法或代码块。
  • 根据使用方式的不同,可以分成两种情况:
    • 对象锁:当synchronized修饰一个普通方法或代码块时,使用的是对象级别的锁。在同一时间内,只有一个线程可以访问这个对象的被Synchronized修饰的方法或代码块。
    • 类锁:当synchronized修饰一个静态方法或代码块时,使用的是类级别的锁。在同一时间内,只有一个线程可以访问这个类的被Synchronized修饰的静态方法或代码块。
  • 举例:假设有一个共享资源counter和两个线程同时对其进行操作。如果不使用synchronized关键字进行同步,可能会导致并发问题,如数据不一致。但是,如果使用Synchronized关键字对操作counter的方法或代码块进行同步,可以确保在任意时间点只有一个线程在操作counter,从而避免了并发问题。

例如:

代码语言:java复制
public class Counter {
    private int counter = 0;
  
    public synchronized void increment() {
        counter  ;
    }
  
    public synchronized void decrement() {
        counter--;
    }
  
    public int getCounter() {
        return counter;
    }
}

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();
  
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i  ) {
                counter.increment();
            }
        });
  
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i  ) {
                counter.decrement();
            }
        });
  
        t1.start();
        t2.start();
  
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
  
        System.out.println(counter.getCounter()); // 输出结果为0
    }
}

这个例子中,counter是一个共享资源,初始值为0。线程t1负责递增counter的值,线程t2负责递减counter的值。通过synchronized关键字对increment()decrement()方法进行同步,保证在任意时间点只有一个线程可以访问这两个方法,避免了并发问题。最终输出结果为0。

0 人点赞