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。