线程同步是为了确保线程安全,所谓线程安全指的是多个线程对同一资源进行访问时,有可能产生数据不一致问题,导致线程访问的资源并不是安全的。如果多线程程序运行结果和单线程运行的结果是一样的,且相关变量的值与预期值一样,则是线程安全的。
Java中与线程同步有关的关键字/类包括:
volatile、synchronized、Lock、AtomicInteger等concurrent包下的原子类。。。等
接下来讨论这几种同步方法。
volatile
volatile一般用在多个线程访问同一个变量时,对该变量进行唯一性约束,volatile保证了变量的可见性,不能保证原子性。
用法(例):private volatile booleanflag = false
保证变量的可见性:volatile本质是告诉JVM当前变量在线程寄存器(工作内存)中的值是不确定的,需要从主存中读取,每个线程对该变量的修改是可见的,当有线程修改该变量时,会立即同步到主存中,其他线程读取的是修改后的最新值。
不能保证原子性:原子性指的是不会被线程调度机制打断的操作,在java中,对基本数据类型的变量的读取和赋值操作是原子性操作。自增/自减操作不是原子性操作。例如:i++,其实是分成三步来操作的:1)从主存中读取i的值;2)执行+1操作;3)回写i的值。volatile关键字并不能保证原子性操作。非原子操作都会产生线程安全的问题,那么如何实现自增/自减的原子性呢?后续将有讲解。
synchronized
synchronized提供了一种独占的加锁方式,是比较常用的线程同步的关键字,一般在“线程安全的单例”中普遍使用。该关键字能够保证代码块的同步性和方法层面的同步。
用法(例):1)代码块同步
//使用synchronized关键字实现线程安全的单例模式
private static Singleton instance;
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class)
{
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
privateSingleton(){ }
2)方法同步
public static synchronized Singleton getInstance2(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
private Singleton(){ }
很多人说synchronized在性能上存在较大问题,但并没有真实环境产生的数据比较说明,因此在这里不好讨论性能问题。
volatile和synchronized的区别
1) volatile通过变量的可见性,指定线程必须从主存中读取变量的最新值;synchronized通过阻塞线程的方式,只有当前线程能访问该变量,锁定了当前变量。
2) volatile使用在变量级别;synchronized可以使用在变量、方法、类级别
3) volatile不会造成线程阻塞;synchronized可能会造成线程阻塞
4) volatile不能保证原子性;synchronized能保证原子性
5) volatile标记的变量不会被编译器优化;synchronized标记的变量有可能会被编译器优化(指令重排)。
如何保证自增/自减的原子性
1) 使用java.util.concurrent包下提供的原子类,如AtomicInteger、AtomicLong、AtomicReference等。
用法:
AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.getAndIncrement();//实现原子自增
atomicInteger.getAndDecrement();//实现原子自减
2)使用synchronized同步代码块
Synchronized(this){
value++;
}
3)使用Lock显示锁同步代码块
private Lock lock = new ReentrantLock();
private final int incrementAndGet(){
lock.lock();
try
{
return value++;
}
finally
{
// TODO: handle finally clause
lock.unlock();
}
}