1、在构造函数中启动线程
我在很多代码中都看到这样的问题,在构造函数中启动一个线程,类似这样:
public
class
A{
public
A(){
this
.x
=
1
;
this
.y
=
2
;
this
.thread
=
new
MyThread();
this
.thread.start();
}
}
这个会引起什么问题呢?如果有个类B继承了类A,依据java类初始化的顺序,A的构造函数一定会在B的构造函数调用前被调用,那么thread线程也将在B被完全初始化之前启动,当thread运行时使用到了类A中的某些变量,那么就可能使用的不是你预期中的值,因为在B的构造函数中你可能赋给这些变量新的值。也就是说此时将有两个线程在使用这些变量,而这些变量却没有同步。
解决这个问题有两个办法:将A设置为final,不可继承;或者提供单独的start方法用来启动线程,而不是放在构造函数中。
2、不完全的同步
都知道对一个变量同步的有效方式是用synchronized保护起来,synchronized可能是对象锁,也可能是类锁,看你是类方法还是实例方法。但是,当你将某个变量在A方法中同步,那么在变量出现的其他地方,你也需要同步,除非你允许弱可见性甚至产生错误值。类似这样的代码:
class
A{
int
x;
public
int
getX(){
return
x;
}
public
synchronized
void
setX(
int
x)
{
this
.x
=
x;
}
}
x的setter方法有同步,然而getter方法却没有,那么就无法保证其他线程通过getX得到的x是最新的值。事实上,这里的setX的同步是没有必要的,因为对int的写入是原子的,这一点JVM规范已经保证,多个同步没有任何意义;当然,如果这里不是int,而是double或者long,那么getX和setX都将需要同步,因为double和long都是64位,写入和读取都是分成两个32位来进行(这一点取决于jvm的实现,有的jvm实现可能保证对long和double的read、write是原子的),没有保证原子性。类似上面这样的代码,其实都可以通过声明变量为volatile来解决。
3、在使用某个对象当锁时,改变了对象的引用,导致同步失效。
这也是很常见的错误,类似下面的代码:
synchronized
(array[0])
{
......
array[0]=new A();
......
}
同步块使用array[0]作为锁,然而在同步块中却改变了array[0]指向的引用。分析下这个场景,第一个线程获取了array[0]的锁,第二个线程因为无法获取array[0]而等待,在改变了array[0]的引用后,第三个线程获取了新的array[0]的锁,第一和第三两个线程持有的锁是不一样的,同步互斥的目的就完全没有达到了。这样代码的修改,通常是将锁声明为final变量或者引入业务无关的锁对象,保证在同步块内不会被修改引用。