多线程陷阱(所有静态初始化块中的代码不一定是类初始化操作)
2011-10-23 21:47
274 查看
大家先看一个程序:
public class StaticThreadInit { static { Thread t = new Thread() { public void run() { System.out.println("进入run方法"); System.out.println("1------" + website); website = "www.leegang.org"; System.out.println("2------" + website); System.out.println("退出run方法"); } }; t.start(); try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } static String website = "www.crazyit.org"; public static void main(String args[]) { System.out.println("main:" + StaticThreadInit.website); } }运行结果:[/code]
进入run方法
为什么会这样呢?website的值为什么没打印出来呢?下边我们来分析下这段程序的执行过程:
main线程试图访问StaticThreadInit.website的值,此时StaticThreadInit尚未被初始化,因此main线程开始对该类执行初始化。初始化过程主要分为两个步骤:一是为该类所有静态field分配内存,二是调用静态初始化块的代码执行初始化。因此main线程首先会为StaticThreadInit类的website field分配内存空间,此时的website的值为null,接着,main线程开始执行StaticThreadInit类的静态初始化块。该代码块创建并启动一个新的线程,并调用了新线程的join()方法,这意味着main线程必须等待新线程执行结束后才能向下执行。新线程开始执行之后,首先执行System.out.println("进入run方法");代码,这就是运行该程序时看到的第一行输出。接着程序试图执行System.out.println(website);问题出现了,StaticThreadInit类正由main线程执行初始化,因此新线程会等待main线程对StaticThreadInit类执行初始化结束。这时候就满足了死锁条件:两个线程互相等待对方执行,因此都不能向下执行。因此程序执行到此就出现了死锁,程序没法执行下去了。
出现死锁的关键原因是程序调用了t.join()。下面分析将t.join()注释后的程序运行情况: main:www.crazyit.org 进入run方法 1------www.crazyit.org 2------www.leegang.org 退出run方法 下面分析下执行过程: main线程进入StaticThreadInit静态初始化之后,同样创建并启动新的线程,这次主线程不会等待新线程,此时新线程只是处于就绪状态,还未进入运行状态。main线程继续执行初始化操作,当完成初始化操作之后,main中的输出语句也就可以完成了。接下来新线程才进入运行状态,一次执行run方法。很明显,产生上面运行结果的原因是调用一条线程的start()方法后,该线程并不会立即进入运行状态,他将只是保持在就绪状态。 为了改变这种状态,在t.start()之后立即调用Thread.sleep()暂停当前程序线程,使得新线程立即获得执行的机会,运行结果: 进入run方法 main:www.crazyit.org 1------www.crazyit.org 2------www.leegang.org 退出run方法 下面分析下执行过程: 当main线程创建启动一条新的线程时,然后主线程调用Thread.sleep()暂停自己,使得新线程获得执行机会,但当新线程执行输出website的值时,因为StaticThreadInit类还未初始化完成,因此新线程不得不放弃执行。线程调度器再次切换到main线程,mian线程于是完成website的初始化,至此StaticThreadInit类初始化完成。通常main线程不会立即切换回来执行新的线程,它会执行main方法里的第一行代码。 这里有一个实际问题:静态初始化块里多线程对静态field所赋的值根本不是初始值,它只是一次普通的赋值。再看下边的代码:
public class StaticThreadInit { static { Thread t = new Thread() { public void run() { website = "www.leegang.org"; } }; t.start(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } final static String website; public static void main(String args[]) { System.out.println("main:" + StaticThreadInit.website); } }编译上面的程序就会出错:无法为最终变量website指定值。
从上面的错误提示可以看出:静态初始化块启动的新线程根本不允许为website赋值,这表明,新线程为website的赋值根本不是初始化操作,只是一次普通的赋值。这个程序给我们的教训是:分析一个程序不能仅仅停留在静态的代码上,而是应该从程序执行过程来把握程序的运行细节。 不要认为所有放在静态初始化块中的代码就一定是类初始化操作,静态初始化块中启动新线程的run方法只是新线程的线程执行体,并不是类初始化操作。类似地,不要认为所有在非静态初始化块中的代码就一定是对象初始化操作,非静态初始化块中启动新的线程的run方法只是新线程的线程执行体,并不是对象初始化操作。
相关文章推荐
- 【转】多线程陷阱(所有静态初始化块中的代码不一定是类初始化操作)
- 初始化块和静态初始化块的区别
- 从头认识多线程-2.16 同步静态方法和静态代码块
- 数据结构之队列的基本操作入队出队初始化删除-c++代码实现及运行实例结果
- Java构造时成员初始化的陷阱(Java中的声明和初始化不是一个原子操作)
- ios多线程操作(七)—— GCD延迟操作与一次性代码
- 多线程中局部静态变量初始化的陷阱
- 数据结构之队列的基本操作入队出队初始化删除-c++代码实现及运行实例结果
- 市面上所有号称"虚拟机","防火墙"的实时监控杀毒软件无一不是使用的IFSHOOK技术.但是同时也有一些朋友不断写MAIL给我打听如何实现读写的监控.下面给出用VTOOLSD写的代码.也就是所有实时杀毒软件的奥秘.同时,很多拦截文件操作的软件,例如对目录加
- 用c和mysql API写的简易操作mysql数据库的多线程服务器和客户端代码
- 【转】Java类的初始化顺序 (静态变量、静态初始化块、变量、初始化块、构造器)
- 顺序栈的定义、初始化、出栈、入栈等操作 C++代码实现
- 【转】Java类的初始化顺序 (静态变量、静态初始化块、变量、初始化块、构造器)
- [疯狂Java]面向对象:初始化块、初始化代码、初始化顺序
- Java类的初始化顺序 (静态变量、静态初始化块、变量 )
- SDRAM的初始化与刷新操作---看时序图写代码
- java的初始化块、静态初始化块、构造函数的执行顺序
- Java的初始化块、静态初始化块、构造函数的执行顺序及用途探究
- 数据结构之队列的基本操作入队出队初始化删除-c++代码实现及运行实例结果
- IOS 开发进阶--多线程和网络--对于耗时操作的代码体验