复活JAVA 多线程学习笔记

声明,本篇内容着重于笔记。内容多摘抄于网络上线程大神的文章,我会尽量贴全引用地址。若是没有您看到摘抄了关于您的文章并没有填写出处请联系我。谢谢

摘抄: 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(),则根据单cpu单线程的运行机制,肯定是主线程运行完,再运行子线程,所以会打印 10 1 2 3... 9

增加join函数,运行后发现打印出 1 2 3...9 10  为什么这一结果呢,看注释. join有个设置等待的时间,假设join(10)意思是“喂,当前主线程,给我10ms用cpu时间”,那么10ms过后,主线程拿到cpu完成自己的事儿,放开cpu,子线程继续干活。比如设置个10ms,那么输出结果也许是

复活JAVA 多线程学习笔记_第1张图片

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); } }


我们看到threadStatus是关键点,是0,则代表新线程。

通过底层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 */ } } }


意思都差不多把,我是这么理解的,反正仅仅让start()一次,创建一次线程。


那么如果要在一个实例上产生多个线程(多个线程共同访问同一实例的一些共同资源),我们应该如何做呢?这就是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++);
            }
        }
        
    }
}

输出结果是 0 1 2 .... 28 29

这就说明了,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() interrupted()  isInterrupted()

首先看源码:

interrupt()

 /**
     * Interrupts this thread.
     * 
     * 

Unless 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 interruptible * channel} 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)*/  

* 如果线程被阻塞,它便不能核查共享变量,也就不能停止。这在许多情况下会发生,例如调用
* Object.wait()、ServerSocket.accept()和DatagramSocket.receive()时,他们都可能永
* 久的阻塞线程。即使发生超时,在超时期满之前持续等待也是不可行和不适当的,所以,要使
* 用某种机制使得线程更早地退出被阻塞的状态。很不幸运,不存在这样一种机制对所有的情况
* 都适用,但是,根据情况不同却可以使用特定的技术。使用Thread.interrupt()中断线程正
* 如Example1中所描述的,Thread.interrupt()方法不会中断一个正在运行的线程。这一方法
* 实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更
* 确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,
* 它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。


继续介绍几种重要方法 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同步方法中尽量不要调用线程的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方法和notify/notifyAll方法.这两个(或叫三个)方法都是Object对象的方法,而不是
线程对象的方法.如同锁一样,它们是在线程中调用某一对象上执行的.
    class Test{
        public synchronized void test(){
        //获取条件,int x 要求大于100;
        
            if(x < 100)
                wait();
        }
    }
这里为了说明方法没有加在try{}catch(){}中,如果没有明确在哪个对象上调用wait()方法,则
为this.wait();
假如:
        Test t = new Test();
现在有两个线程都执行到t.test();方法.其中线程A获取了t的对象锁,进入test()方法内.
这时x小于100,所以线程A进入等待.

当一个线程调用了wait方法后,这个线程就进入了这个对象的休息室(waitset),这是一个虚拟的对象,但JVM中一定存在这样的一个数据结构用来记录当前对象中有哪些程线程在等待.当一个线程进入等待时,它就会释放锁,让其它线程来获取这个锁.所以线程B有机会获得了线程A释放的锁,进入test()方法,如果这时x还是小于100,线程B也进入了t的休息室.这两个线程只能等待其它线程调用notity[All]来唤醒.

但是如果调用的是有参数的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  

可以看到 售票厅资源的状态:剩余票的数量,是不正确的。数量忽大忽小,这就是对统一资源进行操作没有控制互斥的结果。
互斥操作的控制,Java提供了关键字synchronized进行的。synchronized可以修饰方法,也可以修饰代码段。其代表的含义就是:进入他修饰的这段代码内的线程必须先去获取一个特定对象的锁定标示,并且虚拟机保证这个标示一次只能被一条线程拥有。通过这两种方式修改上述代码的方法sellOneTicket(),如下:

    /** 
         *  已经进行了互斥控制。这里是通过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 生产了一条消息: 消息来了!  

多线程应用中,同步与互斥用的特别广泛,这两个是必须要理解并掌握的!

实战

多线程编程的一般原则.
[安全性]是多线程编程的首要原则,如果两个以上的线程访问同一对象时,一个线程会损坏另一个线程的数据,这就是违反了安全性原则,这样的程序是不能进入实际应用的.安全性的保证可以通过设计安全的类和程序员的手工控制.如果多个线程对同一对象访问不会危及安全性,这样的类就是线程安全的类,在JAVA中比如String类就被设计为线程安全的类.而如果不是线程安全的类,那么就需要程序员在访问这些类的实例时手工控制它的安全性.


[可行性]是多线程编程的另一个重要原则,如果仅仅实现了安全性,程序却在某一点后不能继续执行或者多个线程发生死锁,那么这样的程序也不能作为真正的多线程程序来应用.相对而言安全性和可行性是相互抵触的,安全性越高的程序,可性行会越低.要综合平衡.

[高性能] 多线程的目的本来就是为了增加程序运行的性能,如果一个多线程完成的工作还不
如单线程完成得快.那就不要应用多线程了.
高性能程序主要有以下几个方面的因素:
数据吞吐率,在一定的时间内所能完成的处理能力.
响应速度,从发出请求到收到响应的时间.
容量,指同时处理雅致同任务的数量.

安全性和可行性是必要条件,如果达到不这两个原则那就不能称为真正的多线程程序.
而高性是多线程编程的目的,也可以说是充要条件.否则,为什么采用多线程编程呢?

[生产者与消费者模式]
    首先以一个生产者和消费者模式来进入实战篇的第一节.

    生产者和消费者模式中保护的是谁?
     多线程编程都在保护着某些对象,这个些对象是"紧俏资源",要被最大限度地利用,这也是
采用多线程方式的理由
.在生产者消费者模式中,我们要保护的是"仓库",在我下面的这个例子中,
就是桌子(table)


    我这个例子的模式完全是生产者-消费者模式,但我换了个名字.厨师-食客模式,这个食
堂中只有1张桌子,同时最多放10个盘子,现在有4个厨师做菜,每做好一盘就往桌子上放(生产者将
产品往仓库中放),而有6个食客不停地吃(消费者消费产品,为了说明问题,他们的食量是无限的).

    一般而言,厨师200-400ms做出一盘菜,而食客要400-600ms吃完一盘.当桌子上放满了10
个盘子后,所有厨师都不能再往桌子上放,而当桌子是没有盘子时,所有的食客都只好等待.


    下面我们来设计这个程序:

    因为我们不知道具体是什么菜,所以叫它food:

    class Food{}

    然后是桌子,因为它要有序地放而且要有序地取(不能两个食客同时争取第三盘菜),所以我们
扩展LinkedList,或者你用聚合把一个LinkedList作为属性也能达到同样的目的,例子中我是用
继承,从构造方法中传入一个可以放置的最大值

    
class Table extends java.util.LinkedList{
  int maxSize;
  public Table(int maxSize){
    this.maxSize = maxSize;
  }
}

现在我们要为它加两个方法,一是厨师往上面放菜的方法,一是食客从桌子上拿菜的方法.

放菜:因为一张桌子由多个厨师放菜,所以厨师放菜的要被同步,如果桌子上已经有十盘菜了.所有厨师
就要等待:

  public synchronized void putFood(Food f){
    while(this.size() >= this.maxSize){
      try{
        this.wait();
      }catch(Exception e){}
    }
    this.add(f);
    notifyAll();
  }

拿菜:同上面,如果桌子上一盘菜也没有,所有食客都要等待:

  public synchronized Food getFood(){
    while(this.size() <= 0){
      try{
        this.wait();
      }catch(Exception e){}
    }
    Food f = (Food)this.removeFirst();
    notifyAll();
    return f;
  }

厨师类:
    由于多个厨师要往一张桌子上放菜,所以他们要操作的桌子应该是同一个对象,我们从构造
方法中将桌子对象传进去以便控制在主线程中只产生一张桌子.

厨师做菜要用一定的时候,我用在make方法中用sleep表示他要消耗和时候,用200加上200的随机数保
证时间有200-400ms中.做好后就要往桌子上放.


这里有一个非常重要的问题一定要注意,就是对什么范围同步的问题,因为产生竞争的是桌子,所以所
有putFood是同步的,而我们不能把厨师自己做菜的时间也放在同步中,因为做菜是各自做的.同样食客
吃菜的时候也不应该同步,只有从桌子中取菜的时候是竞争的,而具体吃的时候是各自在吃.
所以厨师类的代码如下:

class Chef extends Thread{
  Table t;
  Random r = new Random(12345);
  public Chef(Table t){
    this.t = t;
  }
  public void run(){
    while(true){
      Food f = make();
      t.putFood(f);
    }
  }
  private Food make(){

    try{
      Thread.sleep(200+r.nextInt(200));
    }catch(Exception e){}
    return new Food();
  }
}

同理我们产生食客类的代码如下:

class Eater extends Thread{
  Table t;
  Random r = new Random(54321);
  public Eater(Table t){
    this.t = t;
  }
  public void run(){
    while(true){
      Food f = t.getFood();
      eat(f);
    }
  }
  private void eat(Food f){
    
    try{
      Thread.sleep(400+r.nextInt(200));
    }catch(Exception e){}
  }
}


完整的程序在这儿:
package debug;
import java.util.regex.*;
import java.util.*;


class Food{}

class Table extends LinkedList{
  int maxSize;
  public Table(int maxSize){
    this.maxSize = maxSize;
  }
  public synchronized void putFood(Food f){
    while(this.size() >= this.maxSize){
      try{
        this.wait();
      }catch(Exception e){}
    }
    this.add(f);
    notifyAll();
  }
  
  public synchronized Food getFood(){
    while(this.size() <= 0){
      try{
        this.wait();
      }catch(Exception e){}
    }
    Food f = (Food)this.removeFirst();
    notifyAll();
    return f;
  }
}


class Chef extends Thread{
  Table t;
  String name;
  Random r = new Random(12345);
  public Chef(String name,Table t){
    this.t = t;
    this.name = name;
  }
  public void run(){
    while(true){
      Food f = make();
      System.out.println(name+" put a Food:"+f);
      t.putFood(f);
    }
  }
  private Food make(){
    try{
      Thread.sleep(200+r.nextInt(200));
    }catch(Exception e){}
    return new Food();
  }
}

class Eater extends Thread{
  Table t;
  String name;
  Random r = new Random(54321);
  public Eater(String name,Table t){
    this.t = t;
    this.name = name;
  }
  public void run(){
    while(true){
      Food f = t.getFood();
      System.out.println(name+" get a Food:"+f);
      eat(f);
      
    }
  }
  private void eat(Food f){
    
    try{
      Thread.sleep(400+r.nextInt(200));
    }catch(Exception e){}
  }
}

public class Test {
    public static void main(String[] args) throws Exception{
      Table t = new Table(10);
      new Chef("Chef1",t).start();
      new Chef("Chef2",t).start();
      new Chef("Chef3",t).start();
      new Chef("Chef4",t).start();
      new Eater("Eater1",t).start();
      new Eater("Eater2",t).start();
      new Eater("Eater3",t).start();
      new Eater("Eater4",t).start();
      new Eater("Eater5",t).start();
      new Eater("Eater6",t).start();

    }
}


这一个例子中,我们主要关注以下几个方面:
    1.同步方法要保护的对象,本例中是保护桌子,不能同时往上放菜或同时取菜.
    假如我们把putFood方法和getFood方法在厨师类和食客类中实现,那么我们应该如此:
(以putFood为例)

class Chef extends Thread{
  Table t;
  String name;
  public Chef(String name,Table t){
    this.t = t;
    this.name = name;
  }
  public void run(){
    while(true){
      Food f = make();
      System.out.println(name+" put a Food:"+f);
      putFood(f);
    }
  }
  private Food make(){
    Random r = new Random(200);
    try{
      Thread.sleep(200+r.nextInt());
    }catch(Exception e){}
    return new Food();
  }
  public void putFood(Food f){//方法本身不能同步,因为它同步的是this.即Chef的实例

    synchronized (t) {//要保护的是t
      while (t.size() >= t.maxSize) {
        try {
          t.wait();
        }
        catch (Exception e) {}
      }
      t.add(f);
      t.notifyAll();
    }
  }
}
    2.同步的范围,在本例中是放和取两个方法,不能把做菜和吃菜这种各自不相干的工作
放在受保护的范围中.


    3.参与者与容积比.
        对于生产者和消费者的比例,以及桌子所能放置最多菜的数量三者之间的关系
是影响性能的重要因素,如果是过多的生产者在等待,则要增加消费者或减少生产者的数据,反之
则增加生产者或减少消费者的数量.
另外如果桌子有足够的容量可以很大程序提升性能,这种情况下可以同时提高生产者和
消费者的数量,但足够大的容时往往你要有足够大的物理内存.
从这个例子中还可以学习到,使用wait()而不是sleep()因为 wati是释放当前的锁,进入等待状态,而sleep()是不释放当前的锁,进入等待状态,这种睡觉还要抱着PAD的自私行为是另别人十分厌恶的,所以没有特殊情况,我们一般不会在同步方法内使用sleep()的。但是若是非同步方法想实现“等待”,用它就可以了,若是非要用wait也可以,必须把wait放到同步方法内,代码:
public static mySleep(long l){
        Object o = new Object();
        synchronized(o){
            try{
                o.wait(l);    
            }catch(Exception e){}
        }
    }
放心吧,没有人能在这个方法外调用o.notify[All],所以o.wait(l)会一直等到设定的时间才会运行完成.

Thread线程对象只能start()一次,即只能创建一个线程。最重要的原因是(保证数据安全,实现数据共享)
简单说:

    class T extends Thread{
        Object x;
        public void run(){//......;}
    }
T t = new T();
当T的实例t运行后,t所包含的数据x只能被一个t.start();对象共享,除非声明成
static Object x;
 一个t的实例数据只能被一个线程访问.意思是"一个数据实例对应一个线程".

    而假如我们从外部传入数据,比如

    class T extends Thread{
        private Object x;
        public T(Object x){
            this.x = x;
        }

        public void run(){//......;}
    }
这样我们就可以先生成一个x对象传给多个Thread对象,多个线程共同操作一个数据.也就是"一个数据实例对应多个线程". 这个从外部传递进来的 x 就是我们的紧俏资源,我们要保护的对象。
现在我们把数据更好地组织一下,把要操作的数据Object x和要进行的操作一个封装到Runnable的run()方法中,把Runnable实例从外部传给多个Thread对象.这样,我们就有了:
 [一个对象的多个线程]也就是线程池的概念。

所以我们总结下:
一个数据实例可以对应多个Thread线程对象产生的线程
一个对象实例(数据封装在对象里)可以对应一个Runnable对象产生的多个线程

再谈Interrupt()
不客气地说,至少有一半人认为,线程的"中断"就是让线程停止.
如果你也这么认为,那你对多线程编程还没有入门.

在java中,线程的中断(interrupt)只是改变了线程的中断状态,至于这个中断状态改变后
带来的结果,那是无法确定的,有时它更是让停止中的线程继续执行的唯一手段.不但不是
让线程停止运行,反而是继续执行线程的手段.


对于执行一般逻辑的线程,如果调用调用它的interrupt()方法,那么对这个线程没有任何
影响,比如线程a正在执行:
    while(条件) x ++;
这样的语句,如果其它线程调用a.interrupt();那么并不会影响a对象上运行的线程,如果
在其它线程里测试a的中断状态它已经改变,但并不会停止这个线程的运行.

在一个线程对象上调用interrupt()方法,真正有影响的是wait,join,sleep方法,当然这三个
方法包括它们的重载方法.

请注意:[上面这三个方法都会抛出InterruptedException],记住这句话,下面我会重复.
一个线程在调用interrupt()后,自己不会抛出InterruptedException异常,所以你看到
interrupt()并没有抛出这个异常,所以我上面说如果线程a正在执行while(条件) x ++;
你调用a.interrupt();后线程会继续正常地执行下去.

但是,如果一个线程被调用了interrupt()后,它的状态是已中断的.这个状态对于正在执行
wait,join,sleep的线程,却改变了线程的运行结果.

    一.对于wait中等待notify/notifyAll唤醒的线程,其实这个线程已经"暂停"执行,因为
它正在某一对象的休息室中,这时如果它的中断状态被改变,那么它就会抛出异常.
这个InterruptedException异常不是线程抛出的,而是wait方法,也就是对象的wait方法内部
会不断检查在此对象上休息的线程的状态,如果发现哪个线程的状态被置为已中断,则会抛出
InterruptedException,意思就是这个线程不能再等待了,其意义就等同于唤醒它了.

    这里唯一的区别是,被notify/All唤醒的线程会继续执行wait下面的语句,而在wait
中被中断的线程则将控制权交给了catch语句.一些正常的逻辑要被放到catch中来运行.
    但有时这是唯一手段,比如一个线程a在某一对象b的wait中等待唤醒,其它线程必须
获取到对象b的监视锁才能调用b.notify()[All],否则你就无法唤醒线程a,但在任何线程中可
以无条件地调用a.interrupt();来达到这个目的.只是唤醒后的逻辑你要放在catch中,当然同
notify/All一样,继续执行a线程的条件还是要等拿到b对象的监视锁.

    二.对于sleep中的线程,如果你调用了Thread.sleep(一年);现在你后悔了,想让它早
些醒过来,调用interrupt()方法就是唯一手段,只有改变它的中断状态,让它从sleep中将控制
权转到处理异常的catch语句中,然后再由catch中的处理转换到正常的逻辑.同样,地于join中
的线程你也可以这样处理.
    

    对于一般介绍多线程模式的书上,他们会这样来介绍:当一个线程被中断后,在进入
wait,sleep,join方法时会抛出异常.
是的,这一点也没有错,但是这有什么意义呢?如果你知道那个线程的状态已经处于中
断状态,为什么还要让它进入这三个方法呢?当然有时是必须这么做的,但大多数时候没有这么
做的理由,所以我上面主要介绍了在已经调用这三个方法的线程上调用interrupt()方法让它从
这几个方法的"暂停"状态中恢复过来.这个恢复过来就可以包含两个目的:

 一.[可以使线程继续执行],那就是在catch语句中执行醒来后的逻辑,或由catch语句
转回正常的逻辑.总之它是从wait,sleep,join的暂停状态活过来了.

 二.[可以直接停止线程的运行],当然在catch中什么也不处理,或return,那么就完成
了当前线程的使命,可以使在上面"暂停"的状态中立即真正的"停止".


那么如何正确的中断线程?

中断线程

有了上一节[线程的中断],我们就好进行如何[中断线程]了.这绝对不是玩一个文字游戏.
是因为"线程的中断"并不能保证"中断线程",所以我要特别地分为两节来说明.

这里说的"中断线程"意思是"停止线程",而为什么不用"停止线程"这个说法呢?

因为线程有一个明确的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{
   
   }
  }
 }
}

试试这段程序,可以正确工作吗?




你可能感兴趣的:(复活Java)