原子性:表示一个不可分割的操作.Synchronized 同步代码块和同步方法可以确保以原子的方式操作,但是常见的误解是:Synchronized 只能用于实现原子性或者临界区,其实
Synchronized 还有一个更重要的特性:内存可见性(Memery Visibility),我们不仅希望防止某个Thread正在使用对象状态而被其他的Thread修改,而且希望某个线程修改了状态,对其他线程能看到变化。如果没有同步这种情况就无法实现。可以通过Synchronized 或者 类库中的内置锁保证对象安全的发布出来.
可见性:在多线程环境下,当读操作和写操作在不同的的线程中.无法保证读操作能够适当的看到修改后的状态.为了确保多个线程之间堆内存的可见性,必须使用同步机制。
package net.concurrent;
public class NoVisibility {
private static boolean isRead;
private static int number;
static class ReadThread extends Thread {
public void run() {
while (!isRead) {
Thread.yield();
}
System.out.println("ReadThread:" + number);
}
}
static class ReadThread1 extends Thread {
public void run() {
while (!isRead) {
Thread.yield();
}
System.out.println("ReadThread1:" + number);
}
}
static class ReadThread2 extends Thread {
public void run() {
while (!isRead) {
Thread.yield();
}
System.out.println("ReadThread2:" + number);
}
}
static class ReadThread3 extends Thread {
public void run() {
while (!isRead) {
Thread.yield();
}
System.out.println("ReadThread3:" + number);
}
}
static class ReadThread4 extends Thread {
public void run() {
while (!isRead) {
Thread.yield();
}
System.out.println("ReadThread4:" + number);
}
}
public static void main(String args[]) throws InterruptedException {
for (int i = 0; i < 50; i++) {
isRead = false;
new ReadThread().start();
new ReadThread1().start();
new ReadThread2().start();
new ReadThread3().start();
new ReadThread4().start();
number++;
number++;
number++;
number++;
isRead = true;
Thread.currentThread().sleep(1000);
System.out.println("----------------------------------------");
}
}
}
在不同步的情况下,读线程可能会出现死循环,可能会读到isReader,但是Number会是旧数据.这种现象称为:重排序(Reordering).在没有同步的情况下写入Reader,那么读线程看到的顺序可能与写入的的顺序相反.编译器、处理器在运行过程中对操作的顺序进行调整,在缺乏同步的情况下,对内存的操作的的状态变量进行判断,几乎无法得到正确的结果。只要数据在多个线程中进行访问,就要使用同步
数据失效:食物过期了,还可以吃只不过味道查了一些,但是程序中数据失效呢?在WEB应用程序中统计访问命中数,出现数据失效还不算多严重的问题,在其他的情况数据失效可能会导致严重的安全问题和活跃性问题。例如对象的引用链表指针失效,出现意外的异常,被破坏的数据结构,不精确的算法,死循环。那么情况就复杂多了.
非64位的原子操作:在没有进行同步的情况下,读取一个可变、共享的值,可能会读到一个失效的值,至少这个值是由某个线程设置的,不是一个随机值,这种安全性也成为了最低的安全性.最低的安全性适用于绝大多数变量,但也存在一个例外,非volatile的64位变量(double/long).Java内存模型要求:所有的读取和写入操作必须是原子操作,但对于double 和 long 类型来说,JVM 将64位的读操作或者写操作分解为两个32位的操作。我们可能会读取某个值的高32位或者低32位。所以即使不考虑数据失效的情况下,double 和long 在多线程中也是不安全的。所以需要使用volatile声明或者使用锁包括起来.
总结:加锁的行为不仅仅是为了线程互斥,原子操作。更重要的是实现内存的可见性,为了确保所有的线程都能够看到共享变量最新的值,所有的读操作和写操作必须在同一个锁上同步.