您的位置:首页 > 编程语言 > Java开发

最近遇到的一个Java多线程问题

2013-08-03 10:54 393 查看

1. 问题描述

我的code出了一个多线程问题,错误如下:



被同事指出问题出在多线程访问数据上,问题具体如下:项目中线程主要有两个:android自带的UIThread,GLSurfaceView中的GLThread负责渲染场景中所有的图形元素,两个线程同时访问对象内的数据。

代码原型如下:

class GLLinesGroupOverlay extends GLLinesOverlay {
protected ArrayList<GLLinesOverlay> mLineGroup;

// call in UIThread
public void populate() {
synchronized (mLineGroup) {
// 初始化数据
}
}
// call in UIThread
public boolean onTap(float x, float y) {
synchronized (mLineGroup) {
// 点击事件处理
}
}
// call in GLThread
public void draw(GL10 gl) {
synchronized (mLineGroup) {
// 绘制
}
}
}

class GLMultipleAAOverlay extends GLLinesGroupOverlay {

@Override
public void populate() {
// ##### 操作mLineGroup,忘记加锁啦 #####
}
}
基类中对数据成员的操作都进行了加锁,写派生类的时候头脑不是特别清楚了,重写了方法但是忘记加锁,而且GLMultipleAAOverlay的构造和调用也很奇葩,代码如下:

// UI线程中调用构造overlay,添加到GL渲染队列中
GLMultipleAAOverlay mtOverlay = new GLMultipleAAOverlay();
addOverlay(mtOverlay);		// 添加到GL渲染队列中
// ##### 时序问题 #####
mtOverlay.populate();		// 初始化数据。
new了mtOverlay对象立刻添加到渲染队列中,addOverlay(mtOverlay)函数返回后 GLThread线程就不断调用该overlay的draw函数,然后UI线程再调用mtOverlay.populate()函数结果就悲催了,随机性crash,而且不同手机上行为不一致,在我的HTC G10上一直没crash过,在同事的手机上频率很高。

如果调用代码改成:

// UI线程中调用构造overlay,populate完成后添加到GL渲染队列中
GLMultipleAAOverlay mtOverlay = new GLMultipleAAOverlay();
mtOverlay.populate();		// 初始化数据。
addOverlay(mtOverlay);		// 添加到GL渲染队列中
这么调用populate完成后GL渲染才开始,不会有并发访问问题,然而确多了一个坑:如果GLMultipleAAOverlay重写onTap函数,而且忘记加锁就会有并发访问

多线程本身是跟函数调用时序无关的,所以不能把希望季寄托于GLLinesGroupOverlay的所有函数被以某种正确的时序调用,多线程坑的根因出在派生类可以重写基类函数,任意访问数据成员mLineGroup所致。

2. 问题解决办法

想到了两种思路:

方法1:mLineGroup私有化,取代数据protected的方式是一个final protected数据访问接口,而且接口内对数mLineGroup加锁。不过这么搞粒度过小了吧。

方法2:

class GLLinesGroupOverlay extends GLLinesOverlay {
protected ArrayList<GLLinesOverlay> mLineGroup;

// public final interface function

// call in UIThread
public final void populate() {
synchronized (mLineGroup) {
// 初始化数据
populateImpl();
}
}
// call in UIThread
public final boolean onTap(float x, float y) {
synchronized (mLineGroup) {
onTapImpl(x, y);
}
}
// call in GLThread
public final void draw(GL10 gl) {
synchronized (mLineGroup) {
drawImpl(gl);
}
}

// protected function implementation

// ##### 这些函数可以被重写,但是不能加锁 #####
proteced void populateImpl() {
// 操作数组,初始化数据
}

protected boolean onTapImpl(float x, float y) {
// 操作数组,点击事件处理
}

protected boolean void drawImpl(GL10 gl) {
// 操作数组,绘制
}
}

class GLMultipleAAOverlay extends GLLinesGroupOverlay {

@Override
protected void populateImpl() {
// ~~~~~ 操作mLineGroup,不用加锁 ~~~~~
}
}
public final函数做架子,protected的重载函数做里子。架子中考虑了多线程访问问题,里子直接实现逻辑即可。

通过final阻止public接口被重写,protected函数实现接口可以重写,而且不需要也不能加锁(如果有人吃饱了没事干,还要加锁那可能也会有问题)! 

数据被设成protected,希望派生来访问修改,而且希望派生类在加锁的情况下访问。 真心没有万全之策,只能寄希望于写派生类的人头脑清楚点!

3. final后语

Java 数据不想被修改,函数不想被重写 统一采用final。

      final类不能被继承,没有子类,final类中的方法默认是final的。

      final方法不能被子类的方法覆盖,但可以被继承。

      final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

      final不能用于修饰构造方法。

C++ 数据不想被修改const;C++11才开始增加override和final关键字:http://www.devbean.net/2012/05/cpp11-override-final/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多线程 Java final
相关文章推荐