Java多线程的几个重要概念
Java多线程范例图
从中我们可以看到所有的线程都是在JVM里运行的,而且共享一个主内存(图中的Global Memory),同时各自拥有自己的工作内存(图中的Local Variables)。理解Java这一基本的内存模型很重要。
Java线程的生命周期
1、New: 线程对象已经创建完毕,但尚未启动(start()方法),因此还不能运行。
2、Runnable: 调用start()方法之后,线程就处于这种状态。注意,start()之后线程并不在Running状态。只有调度机制(Scheduler)分配给它CPU时间它才进入Running状态。
3、Running:CPU实现调度了该线程,则处 于Running状态。
4、Blocked: 当处于Running状态的线程被sleep()、wait()或者其他原因触发时,它就进入Blocked状态。这时它可以被notify()等唤醒到Runnable状态等Scheduler的调度,否则什么事也不干,停在那里。
5、Dea d: 当run()正常结束或者被强制结束,线程就进入Dead状态。这时线程对象句柄可能存在,start()方法也可以调用,但都不起作用。要重新起动线程,就必须重新New。
原子操作和volatile关键字
原子操作即不会被线程调度机制中断的操作,是不可再分的操作。在Java语言里,对一个变量的原子操作有:
use:将线程工作内存里的拷贝变量上下文传输给线程的执行引擎。只要线程执行一个使用变量值的虚拟机指令时就会发生这一操作。
assign:将线程执行引擎里的值传输给线程工作内存里的拷贝变量。只要线程执行一个给变量赋值的虚拟机指令时就会发生这一操作。
read:(在主内存上执行)将主内存里的变量上下文传送到线程工作内存里以便接下来的load操作使用。
load:(在工作内存上执行)将从主内存执行read操作后变量的值传送到线程工作内存的拷贝变量上。
store:(在工作内存上执行)将工作内存的拷贝变量的上下文传送到主内存以便接下来的write操作使用。
write:(在主内存上执行)将从线程工作内存执行store操作后变量的值传送到主内存的主变量上。
lock:(在线程要与主内存紧密同步时执行)会导致线程在一个特别的锁上获得一个要求权(类似计数)。
unlock:(在线程要与主内存紧密同步时执行)会导致线程在一个特定的锁上释放一个要求权。
volatile修饰符是用于声明一个变量的存取要在主内存里进行,而不是拷贝到工作内存里进行。这样对基本类型的volatile变量的load、store、read和write操作都是原子操作,就算变量的类型是double或long。那么这样 的操作就是线程安全的,无需synchronized的锁机制来保证。但如果除这些操作外,还有其他代码,则不是原子操作,也就无法确保线程安全。例如,递增或递减操作(++、--)就不能使用volatile变量来保证线程安全,因为这些操作实际上包括 load->change->store3个操作在里面。
在JVM 1.2以前,Java内存模型的实际实现决定了变量总是从主内存存取,这样volatile对那些版本来说是可以忽略的。然而随着Java的演化,包括现在的J2SE 5.0,虚拟机的实现变得更复杂,并且引入了新的内存模型和优化,而且这也是未来的趋势。在时新的虚拟机模型实现下,开发者不能假定变量是从主内存里直接存取的。如果希望对某变量的“原子操作”达到线程安全的要求,最安全的作法就是将变量声明为vola tile。
volatile对数组的作用如何呢?答案是仅仅数组的引用本身是volatile的,而数组的元素仍会被存储到本地内存里。目前没有办法指定数组的元素为volatile的。所以,如果多线程访问一个数组元素,则必须使用synchronized来保 护数据。
锁和synchronized关键字
为了同步多线程,Java语言使用监视器(monitors),一种高级的机制来限定某一 时刻只有一个线程执行一段受监视器保护的代码。监视器的行为是通过锁来实现的,每一个对象都有一个锁。
每个线程都有一个工作内存,在里面存放从所有线程共享的主内存里拷贝来的变量。为了访问一个共享的变量,一个线程通常先要获得一个锁并刷新它的工作内存,这将共享的值从主内存被拷贝到工作内存。当线程解锁时将会把工作内存里的值写回主内存。
&n bsp; 一个线程能多次获得对象的锁。也就是说,一个synchronized方法调用了另一个synchronized方法,而后者又调用了另一synchronized方法,诸如此类。JVM会跟踪对象被上锁的次数。如果对象没有被锁住,那么它的计数器应该 为零。当线程第一次获得对象的锁时,计数器为一。线程每获一次对象的锁,计数器就加一。当然,只有第一次获得对象锁的线程才能多次获得锁。线程每退出一个synchronized方法,计数器就减一。等减到零了,对象也就解锁了,这时其它线程就可以使用这 个对象了。
此外每个类还有一个锁(它属于类的Class对象),这样当类的synchronized static方法读取static数据的时候,就不会相互干扰了。
线程之间不直接交互,它们只通过主内存进行交流。
从J2SE 1.5开始,Java有了Lock接口,将使锁的概念更清晰,使线程同步的控制更容易。使用synchronized,你无法控制锁的释放,而使用Lock接口,你将可以lock(),也可以随你需要进行unlock()。
守护线程(Daemon thread)
所谓"守护线程(daemon thread)"是指,只要程序还在运行,它就应该在后台提供某种公共服务的线程,但是守护线程不属于程序的核心部分。因此,当所有非守护线程都运行结束的时候,程序也结束了。相反,只要还有非守护线程在运行,守护线程就不会结束。
停止线程的正确方法 (摘自Think in Java 3rd Edition Chapter 13)
为了降低死锁的发生几率,Java 2放弃了Thread类stop(),suspend()和resume()方法。
之所以要放弃stop()是因为,它不会释放对象的锁,因此如果对象正处于无效状态( 也就是被破坏了),其它线程就可能会看到并且修改它了。这个问题的后果可能非常微秒,因此难以察觉。所以别再用stop()了,相反你应该设置一个旗标(flag)来告诉线程什么时候该停止。
下面是一个简单的例子:
//: c13:Stopping.java
// The safe way to stop a thread.
import java.util.*;
class CanStop
extends Thread
{
// Must be volatile:
private volatile boolean stop = false;
// Must be volatile:
private volatile boolean stop = false;
private
int counter =
0;
public
void
run()
{
while (!stop && counter < 10000) {
System.out.println(counter++);
}
if (stop)
System.out.println( "Detected stop");
}
while (!stop && counter < 10000) {
System.out.println(counter++);
}
if (stop)
System.out.println( "Detected stop");
}
public
void
requestStop()
{
stop = true;
}
}
stop = true;
}
}
public
class Stopping
{
public static void main(String[] args) {
final CanStop stoppable = new CanStop();
stoppable.start();
new Timer( true). schedule( new TimerTask() {
public void run() {
System.out.println( "Requesting stop");
stoppable.requestStop();
}
}, 500); // run() after 500 milliseconds
}
} ///:~
stop必须是volatile的,这样才能确保run()方法能看到它(否则它会使用本地的缓存值)。这个线程的"任务"是打印10,000个数字,所以当counter >= 10000或有人要它停下来的时候,它就结束了。注意requestStop()不是synchronized,因为stop既是boolean(改成true是一个原子操作)又是volatile的。
main()启动完CanStop之后设置了一个Timer,让它过半秒自动调用requestStop()。Timer的构造函数里的true参数的任务是,把这个线程设成守护线程,这样它就不会阻止程序退出了。
public static void main(String[] args) {
final CanStop stoppable = new CanStop();
stoppable.start();
new Timer( true). schedule( new TimerTask() {
public void run() {
System.out.println( "Requesting stop");
stoppable.requestStop();
}
}, 500); // run() after 500 milliseconds
}
} ///:~
stop必须是volatile的,这样才能确保run()方法能看到它(否则它会使用本地的缓存值)。这个线程的"任务"是打印10,000个数字,所以当counter >= 10000或有人要它停下来的时候,它就结束了。注意requestStop()不是synchronized,因为stop既是boolean(改成true是一个原子操作)又是volatile的。
main()启动完CanStop之后设置了一个Timer,让它过半秒自动调用requestStop()。Timer的构造函数里的true参数的任务是,把这个线程设成守护线程,这样它就不会阻止程序退出了。
附:Thread的一些主要方法
// JDK 1.4.2_05 // native表示用其它依赖于平台的语言实现,如C、汇编等
// 静态方法
public static native void yield(); //暂停当前线程的执行去允许其他线程执行。实际会使得自己获得更多CPU时间。
public static native void sleep(long millis) throws InterruptedException
public static void sleep(long millis, int nanos) throws InterruptedException // 目前只实现到毫秒级,纳秒也只是将大于500000的当作1毫秒处理
public static boolean interrupted() // 与isInterrupted的区别是这个会改变线程的状态为interrupted
public static native void yield(); //暂停当前线程的执行去允许其他线程执行。实际会使得自己获得更多CPU时间。
public static native void sleep(long millis) throws InterruptedException
public static void sleep(long millis, int nanos) throws InterruptedException // 目前只实现到毫秒级,纳秒也只是将大于500000的当作1毫秒处理
public static boolean interrupted() // 与isInterrupted的区别是这个会改变线程的状态为interrupted
// 同步方法
public synchronized native void start()
public synchronized native void start()
// 可被覆写的方法
public void run()
public void interrupt()
public boolean isInterrupted()
public void run()
public void interrupt()
public boolean isInterrupted()
// 不可覆写的方法
public final native boolean isAlive()
public final void setPriority(int newPriority)
public final int getPriority()
public final synchronized void join(long millis) throws InterruptedException
public final synchronized void join(long millis, int nanos) throws InterruptedException
public final void join() throws InterruptedException // 等待线程死亡或到了指定的等待时间才继续执行下面的代码
public final void setDaemon(boolean on) // 将线程设置成守护线程
public final boolean isDaemon()
public final native boolean isAlive()
public final void setPriority(int newPriority)
public final int getPriority()
public final synchronized void join(long millis) throws InterruptedException
public final synchronized void join(long millis, int nanos) throws InterruptedException
public final void join() throws InterruptedException // 等待线程死亡或到了指定的等待时间才继续执行下面的代码
public final void setDaemon(boolean on) // 将线程设置成守护线程
public final boolean isDaemon()
附:Object有关线程的方法
public final void wait() throws InterruptedExceptionwait // 调用wait(0)
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException // 实现调用wait(long timeout)
public final native void notify()
public final native void notifyAll()
public final void wait() throws InterruptedExceptionwait // 调用wait(0)
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException // 实现调用wait(long timeout)
public final native void notify()
public final native void notifyAll()
关于线程的参考资料
1、Think in Java 3rd Edition Chapter 13
2、Concurrent Programming In Java 2nd Edition 作者Doug Lea, Addison-Wesley, 2000出版
3、Java Threads 3rd By Scott Oaks, Henry Wong O'Reilly,September 2004
4、Programming for the Java Virtual Machine Chapter 16
5、Java 2 Primer Plus Chapter 18
P.S. 这篇文档是我为了深入理解Java多线程里的一些概念及解决一些问题时的简单汇总,请大家指教交流!