“非线程安全”发生在多个线程对同一对象的实例变量进行并发访问时,结果是产生“脏读”(取得的数据是被别的线程更改过的)。
1.1 实例变量非线程安全
先看下面一个例子:为普通会员与vip会员设置相应的折扣。
1)Discount.java : 是一个接口,里面只有一个setDiscount(String level)方法
2)InstanceVar.java:实现Discount接口,且添加一个实例变量discount
3)ThreadNormal.java 与ThreadVip.java :两个线程类,通过调用Discount类的方法,来设置普通会员与vip会员的折扣。
4)测试:
两个线程同时访问一个没有同步的方法,若同时操作同一对象的实例变量,则可能出现“非线程安全”。若对象仅有一个实例变量,则有可能出现覆盖的情况,如上述例子。若对象有多个实例变量,则可能出现交叉的情况。解决办法是在相应的方法前加synchronized关键字。
1.2 synchronized方法与锁对象
1)在setDiscount(String level)方法前添加synchronized关键字:
2)再进行测试:
上述结果表明,使用synchronized关键字声明的方法是排队执行的。另外需要记住,只有共享资源的读写访问才需要同步化。
1.3 多个对象多个锁
在上面的例子的基础上,看下面的测试:
以上是两个线程分别访问同一个类的两个不同实例对象的同一个同步方法,是异步运行的。也就是说synchronized关键字取得的是对象锁,多个线程访问同一对象的同一个同步方法时,需要“排队”,而访问不同对象时则不需要。
1.4 方法内的变量为线程安全
1)LocalVar.java:实现Discount接口,在setDiscount方法内部有个变量discount。
2)测试:
1.5 补充
1)synchronized锁重入
当一个线程得到一个对象锁时,再次请求此对象锁,是可以再次得到的。也就是说,在一个synchronized方法/块的内部调用本类中的其它synchronized方法/块时,是永远可以得到锁的,否则会造成死锁。
2)出现异常时,锁被自动释放。