饮水思源:原著http://blog.csdn.net/superhill/article/details/7526771
1.线程同步,一个关键字:synchronized
为什么有这个东西呢,假如有一个对象,里面有成员变量和方法,如果有很多线程都想访问它们,有可能造成用户想避免的结果。
我也举那个经典的例子:假如你的银行账户里面有2000块钱,有一天你去银行柜台取钱,取1500,正在你办理的时候,你老婆去了取款机,她也取钱,事先没商量好谁取,所以她也想取1500。如果两个人都取走了1500,合起来就3000了,银行咋办???
我们把这个银行账户当作一个类来看待,里面有一个钱的成员变量,有一个对钱的数量进行加减的方法,一个getMoney的方法。
在柜台取钱和在取款机取钱分别为2个线程。当两个线程同时访问银行账户这个类的对象的时候。都调用了钱的减法运算的方法,并通过getMoney方法拿到了1500块钱,所有人都这样干,于是,银行破产了。
解决方法,同步。
我在方法声明的时候前面加一个synchronized关键字,public synchronized void method(){ },它代表的意思是在执行这个方法的时候当前对象被锁定起来。
Java中的每个对象都有一个lock,当访问某个对象的synchronized方法时,该对象就会被上锁(注意,是对象,不是方法,假如你在这个类中定义了多个方法,如果你的线程访问到了其中的任意一个synchronized方法,那么其它的就暂时不能被访问了,必须等到该对象被解锁以后,即方法执行结束才行)。解锁的意思是值线程执行该方法完毕,或者说过程中抛出了异常。
再换一种说法,就是,一个类中有synchronized方法,如果该类的对象的该方法被访问时,那么整个该对象都被锁定了,但是这个意思是其它非synchronized方法和成员变量还是可以被访问,注意区分这一点。因为synchronized方法会锁定对象,所以一旦有一个synchronized方法被某个线程启动了,那么对象已经被独占了,其它的synchronized方法就不能再同时独占对象了,但是普通方法和成员变量并不独占对象,所以仍然可以被调用。
需要注意的是,如果同步方法里面有sleep方法,它仍然是同步方法的一部分,在它被执行的过程中,锁仍然不会被解开。
其实同步的意思就是上锁,同步方法,进而达到对象上锁的目的。假如有一个数据库,有读和修改2个方法,你可以允许多个线程同时读,但是你不能让多个线程同时改,所以说改的方法要同步,读的方法不需要。(其实这里我更加觉得应该同步的不是方法,而是数据本身,只要有对象访问对象,对象就应该被锁定,避免读的时候有对象要修改,修改的时候有对象要读,甚至是多个对象同时都想改)。还有,如果2个方法都修改了同一个值的话,那么2个方法都应该加同步。
在这里说3个方法,wait,notify,notifyAll。
之前说过一个方法叫做sleep,通常来说你按照自己的经验和感觉要求线程睡眠一定的时间。但是,有时候当你不知道需要线程睡眠多久的时候,sleep方法就不行了,必须使用wait。但是记住,wait只能用于同步方法。用法大概可以这样,比如你可以先进行一个while判断 (不推荐用if,假如有excpetion发生的话,就不再判断直接执行后面的,这样可能还是有问题,所以最好用while,即使exception发生了,仍然会进行判断),如果满足一定条件就this.wait,然后不满足了就this.notify。如果有很多同步方法的话,那么也可以使用notifyAll方法,那么在这个对象上面等着的线程都会被叫醒。
在这个过程中,当线程1把对象1锁好以后,就想去锁对象2,但是不巧,线程2已经把对象2锁上了,也正在尝试去锁对象1。
什么时候结束呢,只有线程1把2个对象都锁上并把方法执行完,并且线程2把2个对象也都锁上并且把方法执行完毕,那么就结束了,但是,谁都不肯放掉已经锁上的对象,所以就没有结果,这种情况就叫做线程死锁。
其中一个解决方法就是加大锁定的粒度,也就是尽量锁大的对象,不要锁得太小,还有尽量不要同时锁2个或2个以上的对象,但是还有待于进一步研究。