同步的一些简单的原则:
1.永远只是在更新对象得成员变量时加锁
2.永远只是在访问可能被更改的成员变量时加锁
3.永远不要在调用其它对象的方法时加锁
public class Test {
private int x;
private int y;
public Test(int x, int y) {
this.x = x;
this.y = y;
}
public synchronized void move() {//修改变量加锁,规则1
x += 10;
y += 10;
}
public void draw(Graphics g) {
int tempX, tempY;
synchronized (this) {//获得变量加锁,规则2
tempX = this.x;
tempY = this.y;
}
g.drawRect(tempX, tempY, 10, 10);//绘图不加锁,规则3
}
}
不变性
如果一个对象的状态不能被改变, 那么它永远也不会遇到由于多个操作以不同的方式改变其状态而导致的冲突和不一致的现象.
具有不变性的最简单的做法就是对象中根本没有数据, 因此他们的方法都是没有状态的, 也就是说这些方法不依赖于任何对象的任何数据.
在构造函数结束之前, 最好禁止访问对象的数据.
volatile关键字
volatile是java提供的一种同步手段,只不过它是轻量级的同步,为什么这么说,因为volatile只能保证多线程的内存可见性,不能保证多线程的执行有序性。而最彻底的同步要保证有序性和可见性,例如synchronized。任何被volatile修饰的变量,都不拷贝副本到工作内存,任何修改都及时写在主存。因此对于Valatile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改是有序的。什么意思呢?假如有这样的代码:
当一个VolatileTest对象被多个线程共享,a的值不一定是正确的,因为a=a+count包含了好几步操作,而此时多个线程的执行是无序的,因为没有任何机制来保证多个线程的执行有序性和原子性。volatile存在的意义是,任何线程对a的修改,都会马上被其他线程读取到,因为直接操作主存,没有线程对工作内存和主存的同步。所以,volatile的使用场景是有限的,在有限的一些情形下可以使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
1)对变量的写操作不依赖于当前值。
2)该变量没有包含在具有其他变量的不变式中
volatile只保证了可见性,所以Volatile适合直接赋值的场景,如
public class VolatileTest{
public volatile int a;
public void setA(int a){
this.a=a;
}
}
在没有volatile声明时,多线程环境下,a的最终值不一定是正确的,因为this.a=a;涉及到给a赋值和将a同步回主存的步骤,这个顺序可能被打乱。如果用volatile声明了,读取主存副本到工作内存和同步a到主存的步骤,相当于是一个原子操作。所以简单来说,volatile适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。这是一种很简单的同步场景,这时候使用volatile的开销将会非常小。