当前位置导航:炫浪网>>网络学院>>编程开发>>JAVA教程>>Java进阶

Java多线程编程的常见陷阱

    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变量或者引入业务无关的锁对象,保证在同步块内不会被修改引用。

共3页 首页 上一页 1 2 3 下一页 尾页 跳转到
相关内容
赞助商链接