为何自定义Thread线程执行7次而非预期的6次?
线程执行次数超出预期的问题分析
我在项目中定义了继承Thread的Hilo3子类,希望它执行指定次数的操作,代码如下:
class Hilo3 extends Thread { @Override public void run() { while (activoHilo3) { try { Thread.sleep(5000); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(Prueba.this, "Hilo3", Toast.LENGTH_SHORT).show(); if(contador1==5){ activoHilo3=false; } contador1++; } }); } catch (InterruptedException e) { e.printStackTrace(); } } } }
预期显示6次"Hilo3"的Toast,但实际运行后显示了7次。明明当contador1等于5时已经把activoHilo3设为false,却还是多执行了一次,原因如下:
核心原因:循环判断与逻辑执行的顺序问题
线程的while (activoHilo3)判断是在每次循环体执行前进行的,而设置activoHilo3=false的逻辑是在Toast显示之后才执行的,完整的执行流程是这样的:
- 初始
contador1=0,activoHilo3=true,进入循环,sleep后显示Toast,contador1变为1 - 重复上述步骤,直到
contador1=5时:- 此时while循环已经通过了判断(进入循环时
activoHilo3还是true) - 显示Toast,然后设置
activoHilo3=false,contador1变为6
- 此时while循环已经通过了判断(进入循环时
- 线程继续执行下一次循环:因为上一次循环已经进入了循环体,所以会再次sleep,然后进入UI线程显示第7次Toast,之后才会在while判断时发现
activoHilo3=false,退出循环
另外需要注意:如果activoHilo3和contador1是类的成员变量,没有加volatile修饰的话,线程可能无法及时读取到变量的最新值,这也可能导致逻辑出现偏差。
解决方案
方案1:调整判断逻辑,提前终止Toast显示
把次数判断放在Toast显示之前,避免最后一次不必要的显示:
class Hilo3 extends Thread { @Override public void run() { while (activoHilo3) { try { Thread.sleep(5000); runOnUiThread(new Runnable() { @Override public void run() { // 先判断是否达到次数,达到则终止并返回 if(contador1 >= 5){ activoHilo3 = false; return; } Toast.makeText(Prueba.this, "Hilo3", Toast.LENGTH_SHORT).show(); contador1++; } }); } catch (InterruptedException e) { e.printStackTrace(); } } } }
方案2:用计数器直接控制循环次数(更直观)
放弃布尔标志,直接用for循环控制执行次数,从根源避免逻辑混乱:
class Hilo3 extends Thread { // 直接指定要执行的次数 private static final int EXECUTE_TIMES = 6; @Override public void run() { for(int i = 0; i < EXECUTE_TIMES; i++){ try { Thread.sleep(5000); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(Prueba.this, "Hilo3", Toast.LENGTH_SHORT).show(); } }); } catch (InterruptedException e) { e.printStackTrace(); } } } }
注意事项
如果仍然使用布尔标志和计数器的方式,一定要给activoHilo3和contador1加上volatile关键字,确保线程间的变量可见性:
// 类成员变量定义 private volatile boolean activoHilo3 = true; private volatile int contador1 = 0;
内容的提问来源于stack exchange,提问作者Luis Daniels
相关产品推荐
相关产品推荐

