锁提供了两种主要特性:互斥和可见性。
- 互斥:同一时刻只能一个线程持有某个特定的锁,并访问共享数据
 - 可见性:一个线程在释放锁之前对共享数据的修改,对随后获得该锁的线程是可见的,
 
synchronized是一种锁实现,因此具有互斥和可见性,可以保证线程安全。而volatile变量只能保证可见性,不能代替锁用于线程安全。
synchronized通过加锁保证任何时刻只会有一个线程访问同步代码块。synchronized加锁主要有三种形式:
在锁定对象上加锁:只有获得对象的锁才可以进入代码块
synchronized (object) { ... }在实例方法上加锁:只有获得当前实例对象的锁才可以进入代码块
public synchronized void increase() { base++; }在静态方法上加锁:只有获得类的锁才可以进入代码块
public static synchronized void increase() { base++; }
synchronized除了线程同步、保证线程安全外,还可以保证线程间的可见性和有序性。
public class VolatileSynchronizedDemo {
  public static void main(String[] args) throws InterruptedException {
    CalculateRunnable calculateThread = new CalculateRunnable();
    // they should reference the same runnable
    Thread t1 = new Thread(calculateThread);
    Thread t2 = new Thread(calculateThread);
    t1.start();
    t2.start();
    t1.join();
    t2.join();
  }
  public static class CalculateRunnable implements Runnable {
    private int base = 0;
    public synchronized void increase() {
      base++;
    }
    @Override
    public void run() {
      for (int i = 0; i < 100000; i++) {
        increase();
      }
      System.out.println("base: " + base + ", thread: " + Thread.currentThread().getName());
    }
  }
}
volatile变量提供线程安全需要同时满足以下两个条件:
- 对变量的写操作不依赖于变量的当前值
 - 该变量没有包含在其它变量的不变式中
 
第一个限制条件使volatile变量不能用于线程安全的计数器,即counter++。因为这个操作实际上是:读取-修改-写入组成的组合操作,必须保证原子性,而volatile无法提供原子性。
第二个限制条件使volatile变量不能用于多个变量的比较中,如:start <= end等:
@NotThreadSafe 
public class NumberRange {
    private volatile int lower, upper;
 
    public int getLower() { return lower; }
    public int getUpper() { return upper; }
 
    public void setLower(int value) { 
        if (value > upper) 
            throw new IllegalArgumentException(...);
        lower = value;
    }
 
    public void setUpper(int value) { 
        if (value < lower) 
            throw new IllegalArgumentException(...);
        upper = value;
    }
}
上述代码使用volatile变量lower和upper并不能保证线程安全,因为如果两个线程同时使用不同的值调用方法setLower()和setUpper(),则会导致数据不一致,如初始状态为(0, 10),如果两个线程同时调用setLower(8)和setUpper(5),则会破坏状态约束。
volatile的适用模式:
状态标志:用一个布尔型的volatile变量表示一个状态标志,用于指示发生了一个重要的一次性事件,比如初始化完成:
private volatile boolean initDone = false; public void initWorkDone() { this.initDone = true; System.out.println("init done."); } public void startToWork() { while (true) { if (initDone) { System.out.println("init done, start to work"); break; } } }独立观察:定期发布观察结果,如用一个volatile变量记录系统的最后登录用户/最后访问用户等:
public class UserManager { public volatile String lastUser; public boolean authenticate(String user, String password) { boolean valid = passwordIsValid(user, password); if (valid) { User u = new User(); activeUsers.add(u); lastUser = user; } return valid; } }
参考: