问题
如何使用外部变量初始化子线程
来源
写C#的时候发现的问题,现在用Java来说明一下(为什么用Java?)。通常在创建子线程的时候需要向子线程引入一些外部变量来进行初始化,这时候可以使用2种方式来通过编译(仅仅通过编译而已),那么哪种方法才是正确的呢?
代码
import java.util.Calendar;
import java.util.Random;
public class Main {
int tInt;
String tString;
final int Test_Cnt = 10;
public static void main(String[] args) {
// new Main().threadTestA();
// new Main().threadTestB();
// new Main().threadTestC();
// new Main().threadTestD();
// new Main().threadTestE();
}
public void threadTestA() {
Thread[] threads = new Thread[Test_Cnt];
for(int i = 0; i < Test_Cnt; ++i) {
tString = String.format("thread %d", i); // (1)
threads[i] = new Thread() {
@Override
public void run()
{
Random r = new Random(Calendar.getInstance().getTimeInMillis());
try { Thread.sleep(1000 + r.nextInt(2000)); } catch (InterruptedException e) { }
System.out.println(tString);
}
};
}
for(Thread thr : threads) thr.start();
for(Thread thr : threads) try { thr.join(); } catch (InterruptedException e) { }
}
public void threadTestB() {
Thread[] threads = new Thread[Test_Cnt];
for(int i = 0; i < Test_Cnt; ++i) {
tInt = i; // (2)
threads[i] = new Thread() {
@Override
public void run()
{
Random r = new Random(Calendar.getInstance().getTimeInMillis());
try { Thread.sleep(1000 + r.nextInt(2000)); } catch (InterruptedException e) { }
System.out.println(tInt);
}
};
}
for(Thread thr : threads) thr.start();
for(Thread thr : threads) try { thr.join(); } catch (InterruptedException e) { }
}
public void threadTestC() {
Thread[] threads = new Thread[Test_Cnt];
for(int i = 0; i < Test_Cnt; ++i) {
final String ftString = String.format("thread %d", i); // (3)
threads[i] = new Thread() {
@Override
public void run()
{
Random r = new Random(Calendar.getInstance().getTimeInMillis());
try { Thread.sleep(1000 + r.nextInt(2000)); } catch (InterruptedException e) { }
System.out.println(ftString);
}
};
}
for(Thread thr : threads) thr.start();
for(Thread thr : threads) try { thr.join(); } catch (InterruptedException e) { }
}
public void threadTestD() {
Thread[] threads = new Thread[Test_Cnt];
for(int i = 0; i < Test_Cnt; ++i) {
final int itInt = i; // (4)
threads[i] = new Thread() {
@Override
public void run()
{
Random r = new Random(Calendar.getInstance().getTimeInMillis());
try { Thread.sleep(1000 + r.nextInt(2000)); } catch (InterruptedException e) { }
System.out.println(itInt);
}
};
}
for(Thread thr : threads) thr.start();
for(Thread thr : threads) try { thr.join(); } catch (InterruptedException e) { }
}
public void threadTestE() {
Thread[] threads = new Thread[Test_Cnt];
for(int i = 0; i < Test_Cnt; ++i) {
tInt = i; // (5)
threads[i] = new Thread() {
@Override
public void run()
{
int rInt = tInt; // (5)
Random r = new Random(Calendar.getInstance().getTimeInMillis());
try { Thread.sleep(1000 + r.nextInt(2000)); } catch (InterruptedException e) { }
System.out.println(rInt);
}
};
}
for(Thread thr : threads) thr.start();
for(Thread thr : threads) try { thr.join(); } catch (InterruptedException e) { }
}
}
说明
主要区别使用 // (num)
形式表明了。选用 String
和 int
是为了验证值类型和引用类型的区别。
结果表明 threadTestA
和 threadTestB
都不能正确执行,更具体的说它们输出的数字都是9。因为这10个线程并没有在定义时获得变量 i
的值,它们被要求在需要该值时向外部的成员变量 sInt
和 sString
取值,而在生成线程的循环中这2个变量就已经被修改的面目全非了(poi~了相了),当这10个线程开始执行的时候,自然获取到的是sInt
和 sString
在最后的修改中保留下的值。
而 threadTestC
和 threadTestD
可以如期运行。因为 final
的约束,局部变量被赋值后即不可修改,从而保证了各子线程在取值时能够取到其上下文中定义的值(按理说局部变量应该分配在栈中,但是我感觉这种被 final
修饰的局部变量应该被分配到了堆里,当然我目前没能力证明囧~) ,看上去和闭包有点像了,毕竟内部类依赖外部类中的局部变量,从而延长了外部类局部变量的生命周期。
threadThreadE
也不能正确执行,脑抽的时候写的,没什么参考价值。
总结
所以说需要使用外部变量初始化子线程的时候需要使用 final
进行修饰,当然语法已经这么限制了(对于局部变量),而且这个语法针对的(不是某个类 我是说Java的各种类都是辣鸡 大雾)是内部类,而不是单独为线程服务。使用外部成员变量的时候务必小心上文中的那种方式,很可能会产生N个一样的子线程。当然如果只生成1个子线程,那么这2种方法都没有问题,需要考虑的只有主线程和子线程对成员变量的竞争而已。