在做并发开发的时候,为了保证某对象的线程安全,一般都会对其用Synchronized进行保护,比如:
synchronized(obj) {
// Do something with obj.
}
当所有对obj进行的操作,都用同步块保护时,我们一般认为其是线程安全的。
但这个线程安全到底有多安全呢?请先看看这个简单的例子:
代码实现很简单,两个线程同时修改一个对象的属性,在其属性原值基础上进行计算,然后再回写;
但是这里面故意使用了 obj.wait(),实际上造成对synchronized同步锁的释放。
public class SynchronizedWrongUse {
public static void main(String[] args) throws Exception {
// 初始化
SomeoneObject obj = new SomeoneObject();
Worker a = new Worker("A", obj); // 负责给obj +50
Worker b = new Worker("B", obj); // 负责给obj +50
// 启动线程
a.start();
b.start();
// 等待线程启动
Thread.sleep(100);
// 唤醒obj
synchronized (obj) {
obj.notifyAll();
System.out.println("NotifyAll done.");
}
// 等待线程结束
a.join();
b.join();
//输出结果
System.out.println("Amount: " + obj.getAmount()); // 只有50
}
/**
* 并发线程,仅负责给obj +50
*/
static class Worker extends Thread {
private SomeoneObject obj;
public Worker(String name, SomeoneObject obj) {
super(name);
this.obj = obj;
}
public void run() {
System.out.println("Worker[" + this.getName() + "] started.");
synchronized (obj) { // 为保证对obj操作的互斥性,加同步锁
int amount = obj.getAmount();
amount += 50; // 增加50
try {
obj.wait(); // 等待主线程通知(这里将释放obj的同步锁)
} catch (InterruptedException e) {
return;
}
obj.setAmount(amount);
System.out.println("Worker[" + this.getName() + "] setting done.");
}
}
}
/**
* 某对象
*/
static class SomeoneObject {
int amount; // 属性
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
}
}
接触过事务的人应该能很快理解,在synchronized块中,未必能绝对保证事务一致性;因为wait释放了同步锁。
当然,避免这种情况发生一般来说也很简单:
1、尽量不要将基于原有状态的计算过程被中断,也就是你别在 amount += 50; 中间插入wait;
2、可以做乐观检查,也就是set之前重新检查下当前状态是否与原状态保持一致,不一致就要重新发起计算。