摘抄: http://blog.csdn.net/axman
http://blog.csdn.net/budapest
http://blog.csdn.net/drifterj
多线程编程的目的,就是"最大限度地利用CPU资源"
线程对象是可以产生线程的对象.比如在java平台中Thread对象,Runnable对象.在JAVA中,线程对象是JVM产生的一个普通的Object子类.
线程,是指正在执行的一个指令序列.在java平台上是指从一个线程对象的start()开始.运行run方法体中的那一段相对独立的过程.线程是CPU分配给这个对象的一个运行过程.我们说的这个线程在干什么,不是说一个线程对象在干什么,而是这个运行过程在干什么.
并发(concurrent)是指.在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发.
并行(parallel)是指在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行.
引入一个主要的方法. Thread.join(),看代码片段
public class Thread03 {
public static void main(String[] args) {
SubThread t = new SubThread();
t.start();
// 在线程对象a上调用join()方法,就是让当前正在执行的线程等待线程对象a对应的线程运行完成后
// 才继续运行
try {
t.join(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("10");
}
public static class SubThread extends Thread{
public int i = 0 ;
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
System.out.println(""+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
增加join函数,运行后发现打印出 1 2 3...9 10 为什么这一结果呢,看注释. join有个设置等待的时间,假设join(10)意思是“喂,当前主线程,给我10ms用cpu时间”,那么10ms过后,主线程拿到cpu完成自己的事儿,放开cpu,子线程继续干活。比如设置个10ms,那么输出结果也许是
join()默认不指定时间,则为0,0代表永远等待,直到子线程死掉。另外时间不要为负的,没有意义且会异常。
通过Thread实例的start(),一个Thread的实例只能产生一个线程.
线程对象一旦Start() 就创建了一个线程,且该线程对象只能产生一个线程。
JDK1.6源码:
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the run
method of this thread.
*
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* start
method) and the other thread (which executes its
* run
method).
*
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0 || this != me)
throw new IllegalThreadStateException();
group.add(this);
start0();
if (stopBeforeStart) {
stop0(throwableFromStop);
}
}
通过底层VM控制状态值
/**
* Returns the state of this thread.
* This method is designed for use in monitoring of the system state,
* not for synchronization control.
*
* @return this thread's state.
* @since 1.5
*/
public State getState() {
// get current thread state
return sun.misc.VM.toThreadState(threadStatus);
}
JDK1.5源码 使用的是started
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the run
method of this thread.
*
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* start
method) and the other thread (which executes its
* run
method).
*
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see java.lang.Thread#run()
* @see java.lang.Thread#stop()
*/
public synchronized void start() {
if (started)
throw new IllegalThreadStateException();
started = true;
group.add(this);
start0();
}
有兴趣再看看JDK 1.7源码
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the run
method of this thread.
*
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* start
method) and the other thread (which executes its
* run
method).
*
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
那么如果要在一个实例上产生多个线程(多个线程共同访问同一实例的一些共同资源),我们应该如何做呢?这就是Runnable接口给我们带来的伟大的功能.
public class Runnable01 {
public static void main(String[] args) {
//正如它的名字一样,Runnable的实例是可运行的,但它自己并不能直接运行,它需要被Thread对象来
//包装才行运行
MyRunnable r = new MyRunnable();
for (int i = 0; i < 3; i++) {
new Thread(r).start();
}
}
public static class MyRunnable implements Runnable{
int x = 0;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(x++);
}
}
}
}
这就说明了,3个线程,都在同一个 r 对象上执行,使用的是同一个x对象。同一实例(Runnable实例)的多个线程.。注意,因为这个例子是在单CPU上运行的,所以没有对多个线程同时操作共同的对象进行同步.这里是为了说明的方便而简化了同步,而真正的环境中你无法预知程序会在什么环境下运行,所以一定要考虑同步.
一个线程对象生成后,如果要产生一个执行的线程,就一定要调用它的start()方法.在介绍这个方法时不得不同时说明run方法.其实线程对象的run方法完全是一个接口回调方法,它是你这个线程对象要完成的具体逻辑.简单说你要做什么就你在run中完成,而如何做,什么时候做就不需要你控制了,你只要调用start()方法,JVM就会管理这个线程对象让它产生一个线程并注册到线程处理系统中(线程栈).
从表面上看,start()方法调用了run()方法,事实上,start()方法并没有直接调用run方法.在JDK1.5以前start()方法是本地方法,它如何最终调用run方法已经不是JAVA程序员所能了解的.而在JDK1.5中,原来的那个本地start()方法被start0()代替,另个一个纯JAVA的start()中调用本地方法start0(),而在start()方法中做了一个验证,就是对一个全局变量(对象变量)started做检验,如果为true,则start()抛出异常,不会调用本地方法start0(),否则,先将该变量设有true,然后调用start0()
从中我们可以看到这个为了控制一个线程对象只能运行成功一次start()方法.这是因为线程的运行要获取当前环境,包括安全,父线程的权限,优先级等条件,如果一个线程对象可以运行多次,那么定义一个static的线程在一个环境中获取相应权限和优先级,运行完成后它在另一个环境中利用原来的权限和优先级等属性在当前环境中运行,这样就造成无法预知的结果.简单说来,让一个线程对象只能成功运行一次,是基于对线程管理的需要.
start()方法最本质的功能是从CPU中申请另一个线程空间来执行run()方法中的代码,它和当前的线程是两条线,在相对独立的线程空间运行,也就是说,如果你直接调用线程对象的run()方法,当然也会执行,但那是在当前线程中执行,run()方法执行完成后继续执行下面的代码.而调用start()方法后,run()方法的代码会和当前线程并发(单CPU)或并行(多CPU)执行.所以请记住一句话[调用线程对象的run方法不会产生一个新的线程],虽然可以达到相同的执行结果,但执行过程和执行效率不同.
首先看源码:
interrupt()
/** * Interrupts this thread. * *
interruptible * channelUnless the current thread is interrupting itself, which is * always permitted, the {@link #checkAccess() checkAccess} method * of this thread is invoked, which may cause a {@link * SecurityException} to be thrown. * *
If this thread is blocked in an invocation of the {@link * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link * Object#wait(long, int) wait(long, int)} methods of the {@link Object} * class, or of the {@link #join()}, {@link #join(long)}, {@link * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. * *
If this thread is blocked in an I/O operation upon an {@link * java.nio.channels.InterruptibleChannel
} then the channel will be closed, the thread's interrupt * status will be set, and the thread will receive a {@link * java.nio.channels.ClosedByInterruptException}. * *
If this thread is blocked in a {@link java.nio.channels.Selector} * then the thread's interrupt status will be set and it will return * immediately from the selection operation, possibly with a non-zero * value, just as if the selector's {@link * java.nio.channels.Selector#wakeup wakeup} method were invoked. * *
If none of the previous conditions hold then this thread's interrupt * status will be set.
* *Interrupting a thread that is not alive need not have any effect. * * @throws SecurityException * if the current thread cannot modify this thread * * @revised 6.0 * @spec JSR-51 * 注意标红的,只是改变中断标识,实际线程还是在跑的 */ public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(); return; } } interrupt0(); }
interrupted()
/**
* Tests whether the current thread has been interrupted. The
* interrupted status of the thread is cleared by this method. In
* other words, if this method were to be called twice in succession, the
* second call would return false (unless the current thread were
* interrupted again, after the first call had cleared its interrupted
* status and before the second call had examined it).
*
* A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return true
if the current thread has been interrupted;
* false
otherwise.
* @see #isInterrupted()
* @revised 6.0
* 上述大概意思是说,你只要理解返回ture代表,这个线程之前被interupt就行.
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
isinterrupted()
/**
* Tests whether this thread has been interrupted. The interrupted
* status of the thread is unaffected by this method.
*
* A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return true
if this thread has been interrupted;
* false
otherwise.
* @see #interrupted()
* @revised 6.0
*/
public boolean isInterrupted() {
return isInterrupted(false);
}
大多数人以为,一个线程象调用了interrupt()方法,那它对应的线程就应该被中断而抛出异常,事实中,当一个线程对象调用interrupt()方法,它对应的线程并没有被中断,只是改变了它的中断状态.使当前线程的状态变以中断状态,如果没有其它影响,线程还会自己继续执行. 只有当线程执行到sleep,wait,join等方法时,或者自己检查中断状态而抛出异常的情况下,线程才会抛出异常.
如果线程对象调用interrupt()后它对应的线程就立即中断,那么interrupted()方法就不可能执行.因为interrupted()方法是一个static方法,就是说只能在当前线程上调用,而如果一个线程interrupt()后它已经中断了,那它又如何让自己interrupted()?
以上总结一句话: 没有占用CPU运行的线程是不可能给自己的中断状态置位的,也不可能调用interrupted()
正因为一个线程调用interrupt()后只是改变了中断状态,它可以继续执行下去,在没有调用sleep,wait,join等法或自己抛出异常之前,它就可以调用interrupted()来清除中断状态(还会原状) interrupted()方法会检查当前线程的中断状态,如果为"被中断状态"则改变当前线程为"非中断状态"并返回true,如果为"非中断状态"则返回false,它不仅检查当前线程是否为中断状态,而且在保证当前线程回来非中断状态,所以它叫"interrupted",是说中断的状态已经结束(到非中断状态了) isInterrupted()方法则仅仅检查线程对象对应的线程是否是中断状态,并不改变它的状态.
(1) Thread.stop(), Thread.suspend(), Thread.resume() 和Runtime.runFinalizersOnExit() 这些终止线程运行的方法 。这些方法已经被废弃,使用它们是极端不安全的。
(2) Thread.interrupt() 方法是很好的选择。但是使用的时候我们必须好好理解一下它的用处。
//无法中断正在运行的线程代码
class TestRunnable implements Runnable{
public void run(){
while(true)
{
System.out.println( "Thread is running..." );
long time = System.currentTimeMillis();//去系统时间的毫秒数
while((System.currentTimeMillis()-time < 1000)) {
//程序循环1秒钟,不同于sleep(1000)会阻塞进程。
}
}
}
}
public class ThreadDemo{
public static void main(String[] args){
Runnable r=new TestRunnable();
Thread th1=new Thread(r);
th1.start();
th1.interrupt();
}
}
/运行结果:一秒钟打印一次Thread is running...。程序没有终止的任何迹象
上面的代码说明interrupt()并没有中断一个正在运行的线程,或者说让一个running中的线程放弃CPU。那么interrupt到底中断什么。
首先我们看看interrupt究竟在干什么。
当我们调用th1.interrput()的时候,线程th1的中断状态(interrupted status) 会被置位。我们可以通过Thread.currentThread().isInterrupted() 来检查这个布尔型的中断状态。
在Core Java中有这样一句话:"没有任何语言方面的需求要求一个被中断的程序应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断 "。好好体会这句话的含义,看看下面的代码:
//Interrupted的经典使用代码
public void run(){
try{
....
while(!Thread.currentThread().isInterrupted()&& more work to do){
// do more work;
}
}catch(InterruptedException e){
// thread was interrupted during sleep or wait
}
finally{
// cleanup, if required
}
}
很显然,在上面代码中,while循环有一个决定因素就是需要不停的检查自己的中断状态。当外部线程调用该线程的interrupt 时,使得中断状态置位。这是该线程将终止循环,不在执行循环中的do more work了。
这说明: interrupt中断的是线程的某一部分业务逻辑,前提是线程需要检查自己的中断状态(isInterrupted())。
但是当th1被阻塞的时候,比如被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞时。调用它的interrput()方法。可想而知,没有占用CPU运行的线程是不可能给自己的中断状态置位的。这就会产生一个InterruptedException异常。
//中断一个被阻塞的线程代码
class TestRunnable implements Runnable{
public void run(){
try{
Thread.sleep(1000000); //这个线程将被阻塞1000秒
}catch(InterruptedException e){
e.printStackTrace();
//do more work and return.
}
}
}
public class TestDemo2{
public static void main(String[] args) {
Runnable tr=new TestRunnable();
Thread th1=new Thread(tr);
th1.start(); //开始执行分线程
while(true){
th1.interrupt(); //中断这个分线程
}
}
}
/*运行结果:
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at TestRunnable.run(TestDemo2.java:4)
at java.lang.Thread.run(Unknown Source)*/
继续介绍几种重要方法 sleep() join() yield()
首先看源码:
/**
* Causes the currently executing thread object to temporarily pause
* and allow other threads to execute.
*/
public static native void yield();
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
*
* @param millis the length of time to sleep in milliseconds.
* @exception InterruptedException if any thread has interrupted
* the current thread. The interrupted status of the
* current thread is cleared when this exception is thrown.
* @see Object#notify()
*/
public static native void sleep(long millis) throws InterruptedException;
/**
* Waits for this thread to die.
*
* @exception InterruptedException if any thread has interrupted
* the current thread. The interrupted status of the
* current thread is cleared when this exception is thrown.
*/
public final void join() throws InterruptedException {
join(0);
}
Sleep() sleep()方法中是类方法,也就是对当前线程而言的,程序员不能指定某个线程去sleep,只能是当前线程执行到sleep()方法时,睡眠指定的时间(让其它线程运行).事实上也只能是类方法,在当前线程上调用.试想如果你调用一个线程对象的sleep()方法,那么这个对象对应的线程如果不是正在运行,它如何sleep()?所以只有当前线程,因为它正在执行,你才能保证它可以调用sleep()方法.
使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据。注意该方法要捕捉异常。
例如有两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;但是高优先级的线程sleep(500)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
join() 是若场景为: A调用完返回的结果是OK,则继续执行B否则不执行。 那么就好像是个队列,有一环断,整个任务失败。那么代码就可以写为
public class Thread03 {
public static void main(String[] args) {
SubThread t = new SubThread();
t.start();
// 在线程对象a上调用join()方法,就是让当前正在执行的线程等待线程对象a对应的线程运行完成后
// 才继续运行
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("10");
}
public static class SubThread extends Thread{
public int i = 0 ;
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
System.out.println(""+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
输出结果 0.1,2,3....9,10 主线程等待子线程完成后,再做。
原则
:[join是测试其它工作状态的唯一正确方法],我见过很多人,甚至有的是博士生,在处理一项工作时如果另一项工作没有完成,说让当前工作线程sleep(x),我问他,你这个x是如何指定的,你怎么知道是100毫秒而不是99毫秒或是101毫秒?其实这就是OnXXX事件的实质,我们不是要等多长时间才去做什么事,而是当等待的工作正好完成的时候去做.
yield()
该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。
原则:[不是非常必要的情况下,没有理由调用它].调用这个方法不会提高任何效率,只是降低了CPU的总周期。
wait()和notify()、notifyAll()
这三个方法可能一时半会儿有点无法理解,但是一定要记住下面两句话
wait(),notify()/notityAll()方法是普通对象的方法(Object超类中实现),而不是线程对象的方法]
[wait(),notify()/notityAll()方法只能在同步方法中调用]
我们来看下源码:
/**
* Wakes up a single thread that is waiting on this object's
* monitor. If any threads are waiting on this object, one of them
* is chosen to be awakened. The choice is arbitrary and occurs at
* the discretion of the implementation. A thread waits on an object's
* monitor by calling one of the wait
methods.
*
* The awakened thread will not be able to proceed until the current
* thread relinquishes the lock on this object. The awakened thread will
* compete in the usual manner with any other threads that might be
* actively competing to synchronize on this object; for example, the
* awakened thread enjoys no reliable privilege or disadvantage in being
* the next thread to lock this object.
*
* This method should only be called by a thread that is the owner
* of this object's monitor. A thread becomes the owner of the
* object's monitor in one of three ways:
*
* - By executing a synchronized instance method of that object.
*
- By executing the body of a
synchronized
statement
* that synchronizes on the object.
* - For objects of type
Class,
by executing a
* synchronized static method of that class.
*
*
* Only one thread at a time can own an object's monitor.
*
* @exception IllegalMonitorStateException if the current thread is not
* the owner of this object's monitor.
* @see java.lang.Object#notifyAll()
* @see java.lang.Object#wait()
*/
public final native void notify();
/**
* Wakes up all threads that are waiting on this object's monitor. A
* thread waits on an object's monitor by calling one of the
* wait
methods.
*
* The awakened threads will not be able to proceed until the current
* thread relinquishes the lock on this object. The awakened threads
* will compete in the usual manner with any other threads that might
* be actively competing to synchronize on this object; for example,
* the awakened threads enjoy no reliable privilege or disadvantage in
* being the next thread to lock this object.
*
* This method should only be called by a thread that is the owner
* of this object's monitor. See the notify
method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*
* @exception IllegalMonitorStateException if the current thread is not
* the owner of this object's monitor.
* @see java.lang.Object#notify()
* @see java.lang.Object#wait()
*/
public final native void notifyAll();
/**
* Causes the current thread to wait until either another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object, or a
* specified amount of time has elapsed.
*
* The current thread must own this object's monitor.
*
* This method causes the current thread (call it T) to
* place itself in the wait set for this object and then to relinquish
* any and all synchronization claims on this object. Thread T
* becomes disabled for thread scheduling purposes and lies dormant
* until one of four things happens:
*
* - Some other thread invokes the notify method for this
* object and thread T happens to be arbitrarily chosen as
* the thread to be awakened.
*
- Some other thread invokes the notifyAll method for this
* object.
*
- Some other thread {@linkplain Thread#interrupt() interrupts}
* thread T.
*
- The specified amount of real time has elapsed, more or less. If
* timeout is zero, however, then real time is not taken into
* consideration and the thread simply waits until notified.
*
* The thread T is then removed from the wait set for this
* object and re-enabled for thread scheduling. It then competes in the
* usual manner with other threads for the right to synchronize on the
* object; once it has gained control of the object, all its
* synchronization claims on the object are restored to the status quo
* ante - that is, to the situation as of the time that the wait
* method was invoked. Thread T then returns from the
* invocation of the wait method. Thus, on return from the
* wait method, the synchronization state of the object and of
* thread T is exactly as it was when the wait method
* was invoked.
*
* A thread can also wake up without being notified, interrupted, or
* timing out, a so-called spurious wakeup. While this will rarely
* occur in practice, applications must guard against it by testing for
* the condition that should have caused the thread to be awakened, and
* continuing to wait if the condition is not satisfied. In other words,
* waits should always occur in loops, like this one:
*
* synchronized (obj) {
* while ()
* obj.wait(timeout);
* ... // Perform action appropriate to condition
* }
*
* (For more information on this topic, see Section 3.2.3 in Doug Lea's
* "Concurrent Programming in Java (Second Edition)" (Addison-Wesley,
* 2000), or Item 50 in Joshua Bloch's "Effective Java Programming
* Language Guide" (Addison-Wesley, 2001).
*
* If the current thread is {@linkplain java.lang.Thread#interrupt()
* interrupted} by any thread before or while it is waiting, then an
* InterruptedException is thrown. This exception is not
* thrown until the lock status of this object has been restored as
* described above.
*
*
* Note that the wait method, as it places the current thread
* into the wait set for this object, unlocks only this object; any
* other objects on which the current thread may be synchronized remain
* locked while the thread waits.
*
* This method should only be called by a thread that is the owner
* of this object's monitor. See the notify
method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*
* @param timeout the maximum time to wait in milliseconds.
* @exception IllegalArgumentException if the value of timeout is
* negative.
* @exception IllegalMonitorStateException if the current thread is not
* the owner of the object's monitor.
* @exception InterruptedException if any thread interrupted the
* current thread before or while the current thread
* was waiting for a notification. The interrupted
* status of the current thread is cleared when
* this exception is thrown.
* @see java.lang.Object#notify()
* @see java.lang.Object#notifyAll()
*/
public final native void wait(long timeout) throws InterruptedException;
这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用。synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。
wait()方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。
notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
[线程的互斥控制]
多个线程同时操作某一对象时,一个线程对该对象的操作可能会改变其状态,而该状态会影响
另一线程对该对象的真正结果.
这个例子我们在太多的文档中可以看到,就象两个操售票员同时售出同一张票一样.
线程A 线程B
1.线程A在数据库中查询存票,发现票C可以卖出
2.线程A接受用户订票请求,准备出票.
3. 这时切换到了线程B执行
4. 线程B在数据库中查询存票,发现票C可以卖出
5. 线程B将票卖了出去
6.切换到线程A执行,线程A卖了一张已经卖出的票
所以需要一种机制来管理这类问题的发生,当某个线程正在执行一个不可分割的部分时,其它线程不能
不能同时执行这一部分.
像这种控制某一时刻只能有一个线程执行某个执行单元的机制就叫互斥控制或共享互斥(mutual exclusion)
在JAVA中,用synchronized关键字来实现互斥控制(暂时这样认为,JDK1.5已经发展了新的机制)
[synchornized关键字]
把一个单元声明为synchronized,就可以让在同一时间只有一个线程操作该方法.有人说synchornized就是一把锁,事实上它确实存在锁,但是是谁的锁,锁谁,这是一个非常复杂的问题.每个对象只有一把监视锁(monitor lock),一次只能被一个线程获取.当一个线程获取了这一个锁后,其它线程就只能等待这个线程释放锁才能再获取.
那么synchronized关键字到底锁什么?得到了谁的锁?
对于同步块,synchronized获取的是参数中的对象锁:
synchronized(obj){
//...............
}
线程执行到这里时,首先要获取obj这个实例的锁,如果没有获取到线程只能等待.如果多个线程
执行到这里,只能有一个线程获取obj的锁,然后执行{}中的语句,所以,obj对象的作用范围不同,控制程序
不同.
假如:
public void test(){
Object o = new Object();
synchronized(obj){
//...............
}
}
这段程序控制不了任何,多个线程之间执行到Object o = new Object();时会各自产生一个对象然后获取这个对象有监视锁,各自皆大欢喜地执行.
而如果是类的属性:
class Test{
Object o = new Object();
public void test(){
synchronized(o){
//...............
}
}
}
所有执行到Test实例的synchronized(o)的线程,只有一个线程可以获取到监视锁.
有时我们会这样:
public void test(){
synchronized(this){
//...............
}
}
那么所有执行Test实例的线程只能有一个线程执行.而synchornized(o)和
synchronized(this)的范围是不同的,因为执行到Test实例的synchornized(o)的线程等待时,其它线程可以执行
Test实例的synchronized(o1)部分,但多个线程同时只有一个可以执行Test实例的synchornized(this).
而对于 synchronized(Test.class){
//...............
}这样的同步块而言,所有调用Test多个实例的线程赐教只能有一个线程可以执行.
[synchronized方法]
如果一个方法声明为synchronized的,则等同于把在为个方法上调用synchronized(this).
如果一个静态方法被声明为synchronized,则等同于把在为个方法上调用synchronized(类.class).
但是如果调用的是有参数的wait(time)方法,则线程A,B都会在休息室中等待这个时间后自动唤醒.
[为什么真正的应用都是用while(条件)而不用if(条件)]
在实际的编程中我们看到大量的例子都是用
while(x < 100)
wait();go();而不是用if,为什么呢?
在多个线程同时执行时,if(x <100)是不安全的.因为如果线程A和线程B都在t的休息室中等待,这时另一个线程使x==100了,并调用notifyAll方法,线程A继续执行下面的go().而它执行完成后,x有可能又小于100,比如下面的程序中调用了--x,这时切换到线程B,线程B没有继续判断,直接执行go();就产生一个错误的条件,只有while才能保证线程B又继续检查一次.
[notify/notifyAll方法]
这两个方法都是把某个对象上休息区内的线程唤醒,notify只能唤醒一个,但究竟是哪一个不能确定,而notifyAll则唤醒这个对象上的休息室中所有的线程 一般有为了安全性,我们在绝对多数时候应该使用notifiAll(),除非你明确知道只唤醒其中的一个线程. 那么是否是只要调用一个对象的wait()方法,当前线程就进入了这个对象的休息室呢?事实中,要调用一个对象的wait()方法,只有当前线程获取了这个对象的锁,换句话说一定要在这个对象的同步方法或以这个对象为参数的同步块中.
class MyThread extends Thread{
Test t = new Test();
public void run(){
t.test();
System.out.println("Thread say:Hello,World!");
}
}
public class Test {
int x = 0;
public void test(){
if(x==0)
try{
wait();
}catch(Exception e){}
}
public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
这个线程就不会进入t的wait方法而直接打印出Thread say:Hello,World!.而如果改成
public class Test {
int x = 0;
public synchronized void test(){
if(x==0)
try{
wait();
}catch(Exception e){}
}
public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
还记得开始时我们要必须记住的两个原则质之一吗[wait(),notify()/notityAll()方法只能在同步方法中调用] 一旦加入了synchronized,标记为同步方法,则进入他修饰的这段代码内的线程先去获取一个特定对象的锁定标示,并且虚拟机保证这个标示一次只能被一条线程拥有。
我们就可以看到线程一直等待,注意这个线程进入等待后没有其它线程唤醒,除非强行退出
JVM环境,否则它一直等待.所以请记住:
[线程要想调用一个对象的wait()方法就要先获得该对象的监视锁,而一旦调用wait()后又立即释放该锁]
线程互斥的经典例子是 售火车票
线程同步的经典例子是 生产者与消费者
下面分别请看:
线程间互斥应对的是这种场景:多个线程操作同一个资源(即某个对象),为保证线程在对资源的状态(即对象的成员变量)进行一些非原子性操作后,状态仍然是正确的。典型的例子是“售票厅售票应用”。售票厅剩余100张票,10个窗口去卖这些票。这10个窗口,就是10条线程,售票厅就是他们共同操作的资源,其中剩余的100张票就是这个资源的一个状态。线程买票的过程就是去递减这个剩余数量的过程。不进行互斥控制的代码如下:
package cn.test;
public class TicketOffice {
private int ticketNum = 0;
public TicketOffice(int ticketNum) {
super();
this.ticketNum = ticketNum;
}
public int getTicketNum() {
return ticketNum;
}
public void setTicketNum(int ticketNum) {
this.ticketNum = ticketNum;
}
/**
* 售票厅卖票的方法,这个方法操作了售票厅对象唯一的状态--剩余火车票数量。
* 该方法此处并未进行互斥控制。
*/
public void sellOneTicket(){
ticketNum--;
// 打印剩余票的数量
if(ticketNum >= 0){
System.out.println("售票成功,剩余票数: " + ticketNum);
}else{
System.out.println("售票失败,票已售罄!");
}
}
public static void main(String[] args) {
final TicketOffice ticketOffice = new TicketOffice(100);
// 启动10个线程,即10个窗口开始卖票
for(int i=0;i<10;i++){
new Thread(new Runnable(){
@Override
public void run() {
// 当还有剩余票的时候,就去执行
while(ticketOffice.getTicketNum() > 0){
ticketOffice.sellOneTicket();
}
}
}).start();
}
}
}
售票成功,剩余票数: 93
售票成功,剩余票数: 92
售票成功,剩余票数: 91
售票成功,剩余票数: 95
售票成功,剩余票数: 96
售票成功,剩余票数: 87
售票成功,剩余票数: 86
售票成功,剩余票数: 88
售票成功,剩余票数: 89
售票成功,剩余票数: 83
售票成功,剩余票数: 82
售票成功,剩余票数: 81
售票成功,剩余票数: 90
售票成功,剩余票数: 79
售票成功,剩余票数: 93
/**
* 已经进行了互斥控制。这里是通过synchronized修饰整个方法实现的。
* 线程想进入这个方法,必须获取当前对象的锁定表示!
*/
public synchronized void sellOneTicket(){
ticketNum--;
// 打印剩余票的数量
if(ticketNum >= 0){
System.out.println("售票成功,剩余票数: " + ticketNum);
}else{
System.out.println("售票失败,票已售罄!");
}
}
/**
* 已经进行了互斥控制。这里是通过synchronized修饰代码块实现的。线程要想进入修饰的代码块,
* 必须获取lock对象的对象标示。
*/
private Object lock = new Object();
public void sellOneTicket2(){
synchronized(lock){
ticketNum--;
// 打印剩余票的数量
if(ticketNum >= 0){
System.out.println("售票成功,剩余票数: " + ticketNum);
}else{
System.out.println("售票失败,票已售罄!");
}
}
}
售票成功,剩余票数: 99
售票成功,剩余票数: 98
售票成功,剩余票数: 97
售票成功,剩余票数: 96
售票成功,剩余票数: 95
售票成功,剩余票数: 94
售票成功,剩余票数: 93
售票成功,剩余票数: 92
售票成功,剩余票数: 91
售票成功,剩余票数: 90
售票成功,剩余票数: 89
售票成功,剩余票数: 88
售票成功,剩余票数: 87
售票成功,剩余票数: 86
售票成功,剩余票数: 85
售票成功,剩余票数: 84
售票成功,剩余票数: 83
售票成功,剩余票数: 82
同步的概念再于线程间通信,比较典型的例子就是“生产者-消费者问题”。
多个生产者和多个消费者就是多条执行线程,他们共同操作一个数据结构中的数据,数据结构中有时是没有数据的,这个时候消费者应该处于等待状态而不是不断的去访问这个数据结构。这里就涉及到线程间通信(当然此处还涉及到互斥,这里暂不考虑这一点),消费者线程一次消费后发现数据结构空了,就应该处于等待状态,生产者生产数据后,就去唤醒消费者线程开始消费。生产者线程某次生产后发现数据结构已经满了,也应该处于等待状态,消费者消费一条数据后,就去唤醒生产者继续生产。
实现这种线程间同步,可以通过Object类提供的wait,notify, notifyAll 3个方法去进行即可。一个简单的生产者和消费者的例子代码为:
package cn.test;
public class ProducerConsumer {
public static void main(String[] args) {
final MessageQueue mq = new MessageQueue(10);
// 创建3个生产者
for(int p=0;p<3;p++){
new Thread(new Runnable(){
@Override
public void run() {
while(true){
mq.put("消息来了!");
// 生产消息后,休息100毫秒
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "Producer" + p).start();
}
// 创建3个消费者
for(int s=0;s<3;s++){
new Thread(new Runnable(){
@Override
public void run() {
while(true){
mq.get();
// 消费消息后,休息100毫秒
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "Consumer" + s).start();
}
}
/**
* 内部类模拟一个消息队列,生产者和消费者就去操作这个消息队列
*/
private static class MessageQueue{
private String[] messages;// 放置消息的数据结构
private int opIndex; // 将要操作的位置索引
public MessageQueue(int size) {
if(size <= 0){
throw new IllegalArgumentException("消息队列的长度至少为1!");
}
messages = new String[size];
opIndex = 0;
}
public synchronized void put(String message){
// Java中存在线程假醒的情况,此处用while而不是用if!可以参考Java规范!
while(opIndex == messages.length){
// 消息队列已满,生产者需要等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
messages[opIndex] = message;
opIndex++;
System.out.println("生产者 " + Thread.currentThread().getName() + " 生产了一条消息: " + message);
// 生产后,对消费者进行唤醒
notifyAll();
}
public synchronized String get(){
// Java中存在线程假醒的情况,此处用while而不是用if!可以参考Java规范!
while(opIndex == 0){
// 消息队列无消息,消费者需要等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String message = messages[opIndex-1];
opIndex--;
System.out.println("消费者 " + Thread.currentThread().getName() + " 消费了一条消息: " + message);
// 消费后,对生产者进行唤醒
notifyAll();
return message;
}
}
}
消费者 Consumer1 消费了一条消息: 消息来了!
生产者 Producer0 生产了一条消息: 消息来了!
消费者 Consumer0 消费了一条消息: 消息来了!
生产者 Producer2 生产了一条消息: 消息来了!
消费者 Consumer2 消费了一条消息: 消息来了!
生产者 Producer1 生产了一条消息: 消息来了!
消费者 Consumer0 消费了一条消息: 消息来了!
生产者 Producer0 生产了一条消息: 消息来了!
消费者 Consumer1 消费了一条消息: 消息来了!
生产者 Producer2 生产了一条消息: 消息来了!
消费者 Consumer2 消费了一条消息: 消息来了!
生产者 Producer0 生产了一条消息: 消息来了!
消费者 Consumer1 消费了一条消息: 消息来了!
生产者 Producer1 生产了一条消息: 消息来了!
消费者 Consumer0 消费了一条消息: 消息来了!
生产者 Producer2 生产了一条消息: 消息来了!
消费者 Consumer0 消费了一条消息: 消息来了!
生产者 Producer1 生产了一条消息: 消息来了!
生产者 Producer0 生产了一条消息: 消息来了!
消费者 Consumer2 消费了一条消息: 消息来了!
消费者 Consumer1 消费了一条消息: 消息来了!
生产者 Producer1 生产了一条消息: 消息来了!
中断线程
有了上一节[线程的中断],我们就好进行如何[中断线程]了.这绝对不是玩一个文字游戏.
是因为"线程的中断"并不能保证"中断线程",所以我要特别地分为两节来说明.
这里说的"中断线程"意思是"停止线程",而为什么不用"停止线程"这个说法呢?
因为线程有一个明确的stop方法,但它是反对使用的,所以请大家记住,在java中以后不要提
停止线程这个说法,忘记它!
但是,作为介绍线程知识的我,我仍然要告诉你为什么不用"停止线程"的理由.
[停止线程]
当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,
并抛出特殊的ThreadDeath()异常.这里的"立即"因为太"立即"了,就象一个正在摆弄自己的
玩具的孩子,听到大人说快去睡觉去,就放着满地的玩具立即睡觉去了.这样的孩子是不乖的.
假如一个线程正在执行:
synchronized void {
x = 3;
y = 4;
}
由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到
x = 3;时,被调用了 stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整
的残废数据.而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记线程的stop
方法,以后我们再也不要说"停止线程"了.
如何才能"结束"一个线程?
[中断线程]
结束一个线程,我们要分析线程的运行情况.也就是线程正在干什么.如果那个孩子
什么事也没干,那就让他立即去睡觉.而如果那个孩子正在摆弄他的玩具,我们就要让它把玩
具收拾好再睡觉.
所以一个线程从运行到真正的结束,应该有三个阶段:
1.正常运行.
2.处理结束前的工作,也就是准备结束.
3.结束退出.
在我的JDBC专栏中我N次提醒在一个SQL逻辑结束后,无论如何要保证关闭Connnection
那就是在finally从句中进行.同样,线程在结束前的工作应该在finally中来保证线程退出前
一定执行:
try{
正在逻辑
}catch(){}
finally{
清理工作
}
那么如何让一个线程结束呢?既然不能调用stop,可用的只的interrupt()方法.但interrupt()
方法只是改变了线程的运行状态,如何让它退出运行?
对于一般逻辑,只要线程状态为已经中断,我们就可以让它退出,所以这样的语句可以保证
线程在中断后就能结束运行:
while(!isInterrupted()){
正常逻辑
}
这样如果这个线程被调用interrupt()方法,isInterrupted()为true,就会退出运行.但是
如果线程正在执行wait,sleep,join方法,你调用interrupt()方法,这个逻辑就不完全了.
如果一个有经验的程序员来处理线程的运行的结束:
public void run(){
try{
while(!isInterrupted()){
正常工作
}
}
catch(Exception e){
return;
}
finally{
清理工作
}
}
我们看到,如果线程执行一般逻辑在调用innterrupt后.isInterrupted()为true,退出循环后执行
清理工作后结束,即使线程正在wait,sleep,join,也会抛出异常执行清理工作后退出.
这看起来非常好,线程完全按最我们设定的思路在工作.但是,并不是每个程序员都有这种认识,如果
他聪明的自己处理异常会如何?事实上很多或大多数程序员会这样处理:
public void run(){
while(!isInterrupted()){
try{
正常工作
}catch(Exception e){
//nothing
}
finally{
}
}
}
想一想,如果一个正在sleep的线程,在调用interrupt后,会如何?
wait方法检查到isInterrupted()为true,抛出异常,而你又没有处理.而一个抛出了
InterruptedException的线程的状态马上就会被置为非中断状态,如果catch语句没有处理异常,则
下一次循环中isInterrupted()为false,线程会继续执行,可能你N次抛出异常,也无法让线程停止.
那么如何能确保线程真正停止?
在线程同步的时候我们有一个叫"二次惰性检测"(double check),能在提高效率的基础上又
确保线程真正中同步控制中.
那么我把线程正确退出的方法称为"双重安全退出",即不以isInterrupted()为循环条件.而
以一个标记作为循环条件:
class MyThread extend Thread{
private boolean isInterrupted = false;//这一句以后要修改
public void interrupt(){
isInterrupted = true;
super.interrupt();
}
public void run(){
while(!isInterrupted){
try{
正常工作
}catch(Exception e){
//nothing
}
finally{
}
}
}
}
试试这段程序,可以正确工作吗?