我的code出了一个多线程问题,错误如下:
被同事指出问题出在多线程访问数据上,问题具体如下:项目中线程主要有两个:android自带的UIThread,GLSurfaceView中的GLThread负责渲染场景中所有的图形元素,两个线程同时访问对象内的数据。
代码原型如下:
class GLLinesGroupOverlay extends GLLinesOverlay {
protected ArrayList 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所致。
想到了两种思路:
方法1:mLineGroup私有化,取代数据protected的方式是一个final protected数据访问接口,而且接口内对数mLineGroup加锁。不过这么搞粒度过小了吧。
方法2:
class GLLinesGroupOverlay extends GLLinesOverlay {
protected ArrayList 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的重载函数做里子。架子中考虑了多线程访问问题,里子直接实现逻辑即可。
数据被设成protected,希望派生来访问修改,而且希望派生类在加锁的情况下访问。 真心没有万全之策,只能寄希望于写派生类的人头脑清楚点!