Java的内存模型分为主存储器和工作存储器两种。 主存就是实例位置所在的区域,主存为所有线程锁共用;每个线程都拥有自己独立的作业区,称为工作存储器。 当一个线程需要对某对象的一些字段进行操作时,就会把这些需要的信息从主存储器copy到自己的工作存储器,而这个拷贝就叫做工作拷贝。
说到这里大家都可以想象Java是如何保证内存一致的了。主存上面的内容就像是个标准,而每个线程的工作拷贝就像是草稿。草稿上计算好的结果就要放到主存去,这样其他线程也可以看得到,这就是可见性了。当一个线程获得某一对象的锁,他就获得了修改这个对象状态的权利,他开始copy主存的内容到他的工作存储器,这个时候他对工作拷贝的任何修改都没有在主存上,而当他要释放锁的时候就必须把工作拷贝写入到主存去。为什么呢?他如果不写的话主存上的内容还是以前的内容,只不过其他线程不知道有个线程做了一些事情,可以忽略啊。问题就是不能忽略,为了保证每个线程做的事情都被他后面的线程看到,他必须在释放锁之前就把内容写到主存,即使他自己的工作只完成了部分。这样才有先获得锁和后获得锁的区别,先获得锁的线程所做的操作对后获得锁的线程是可见的。
当然线程不是只有在释放锁时才会被工作拷贝写入主存,他可以在对对象的字段进行一次指定之后就进行一次写入,也可以到最后将最终值写入,由处理器决定。
方法的自变量和局部变量为每个线程所独有,所以不用考虑同步。
多线程编程需要解决的两个问题就是线程间的干扰和内存不一致问题,synchronized在这里是恰到好处的,它的两个作用就是“线程的同步”和”内存的同步“。 synchronized所保护的方法或者块被称作临界区,在同一时刻只有一个线程可以访问,就像独木桥,而且是只有一个人可以上去的独木桥。内存的同步指的就是主存储器和工作存储器上的内容的同步,要保证两个内容是一样的。那什么时候去进行同步呢,不可能时时刻刻都去同步吧,那所有线程都在一直看自己有没有跟主存同步,这样不是浪费时间吗?线程会在“欲进入synchronized块”和“欲退出synchronized块”是进行同步,同步包括两方面,一个是把自己的工作拷贝写到主存,一个是从主存读内容。在线程欲进入synchronized时会将工作拷贝中没有写入到主存的内容强制写入主存,在退出synchronized是会做同样的强制写入。不同的是在进入synchronized时线程还会释放工作存储器,这样可以保证新的内容的读入,但是在欲退出synchronized时则不会释放工作存储器,毕竟这个时候还不用考虑读入新的内容,总之下次进入synchronized块时一定会读的。 由此可见主存和工作存储器上的内容并不是总是同步的,而且大多数时候应该是不一样的。内存的同步就是保证需要用到这个内容的时候和主存是一致的,对它做完修改之后保证把内容写入主存。换句话说保证别人写的你看到了,保证你写的别人也看到了。
volatile的与synchronized不同的是它只会进行内存的同步,并不能保证线程的同步。当线程引用valotile字段时,会先从主存copy值到工作存储器,当线程指定值到volatile字段时会copy值到主存。volatile的另一个功能是保证对long和double的指定动作的原子性。指定一个int或者bye之类的基本数据类型都是原子的,只有指定long和double需要分成两次,这就可能导致在多线程的时候出现脏数据,所以在多线程编程中一定要保证对long和double的原子访问。