Java中的多线程

 1.进程和线程

  进程是运行中的程序,每个进程拥有独立的资源,在处理器上可以并发执行,多个进程之间互不影响。
  线程是进程的执行单元,一个进程被初始化后,主线程就被创建了。一个线程可以有自己的堆栈,计数器和局部变量,但系统资源和其父进程的其他线程所共享。一个线程可以创建和撤销另外一个线程,线程本身的调度和管理由进程负责完成。


 2.线程的创建和启动


  创建线程有如下两种方法:


  2.1 继承Thread类创建线程类


  具体步骤如下:
    a. 定义Thread类的子类,并重写run()方法。run()方法的方法体就是线程需要执行的任务;
    b. 创建Thread子类的实例;
    c. 用线程对象的start()方法来启动线程。

 

 

  1. //通过继承Thread类来创建线程类  
  2. public class FirstThread extends Thread  
  3. {  
  4.     private int i ;  
  5.     //重写run方法,run方法的方法体就是线程执行体  
  6.     public void run()  
  7.     {  
  8.         for ( ; i < 100 ; i++ )  
  9.         {  
  10.             //当线程类继承Thread类时,可以直接调用getName()方法来返回当前线程的名。  
  11.             //如果想获取当前线程,直接使用this即可  
  12.             //Thread对象的getName返回当前该线程的名字  
  13.             System.out.println(getName() +  " " + i);  
  14.         }  
  15.     }  
  16.       
  17.     public static void main(String[] args)   
  18.     {  
  19.         for (int i = 0; i < 100;  i++)  
  20.         {  
  21.             //调用Thread的currentThread方法获取当前线程  
  22.             System.out.println(Thread.currentThread().getName() +  " " + i);  
  23.             if (i == 20)  
  24.             {  
  25.                 //创建、并启动第一条线程  
  26.                 new FirstThread().start();  
  27.                 //创建、并启动第二条线程  
  28.                 new FirstThread().start();  
  29.             }  
  30.         }  
  31.     }  

需要注意的是:该程序有三个线程,当i=20时启动另外两个线程。另外,两个线程不能共享实例属性i,他们输     出的i值不连续。


  2.2 实现Runnable接口创建线程类


  具体步骤如下:
  a.  定义Runnable接口实现的类,并重写run()方法;
  b.  创建其实例,并把这个实例作为Thread类的Target来创建Thread对象;
  c.  执行Thread对象的start()方法。

 

  1. //通过实现Runnable接口来创建线程类  
  2. public class SecondThread implements Runnable  
  3. {  
  4.     private int i ;  
  5.     //run方法同样是线程执行体  
  6.     public void run()  
  7.     {  
  8.         for ( ; i < 100 ; i++ )  
  9.         {  
  10.             //当线程类实现Runnable接口时,  
  11.             //如果想获取当前线程,只能用Thread.currentThread()方法。  
  12.             System.out.println(Thread.currentThread().getName() + "  " + i);  
  13.         }  
  14.     }  
  15.       
  16.     public static void main(String[] args)   
  17.     {  
  18.         for (int i = 0; i < 100;  i++)  
  19.         {  
  20.             System.out.println(Thread.currentThread().getName() + "  " + i);  
  21.             if (i == 20)  
  22.             {  
  23.                 SecondThread st = new SecondThread();  
  24.                 //通过new Thread(target , name)方法创建新线程  
  25.                 new Thread(st , "新线程1").start();  
  26.                 new Thread(st , "新线程2").start();  
  27.             }  
  28.         }  
  29.     }  

   以同一个实现Runnable接口的线程类实例为target对象的多条线程,可以共享线程类的实例属性。上面代码中     的两个线程输出的i值连续。

3. 线程的生命周期

   线程的生命周期如下图所示:

 

    需要注意如下几点:

    a. 当使用new关键字创建一个线程时,该线程就处于新建状态;而该对象调用了start()之后,该线程就处于就绪状态,表示该线程可以运行,至于何时运行,取决于Java虚拟机中线程调度器的调度。

    b. 启动线程调用的是start()方法而非run()方法。调用start()方法启动线程,系统会把该run()方法当做线程的执行体去处理,否则就不会把其作为一个线程去看,从而立刻执行。

    c. 其他线程和主线程具有同等地位,不会因为主线程的结束而随之结束。不能对已经死亡的线程调用start()方法。

  4. 线程的控制

  下面是一些控制线程的常用方法: 

    a. join

    它的作用是使得一个线程等待另外一个线程执行完毕在开始继续执行。在需要等待的线程中调用被等待线程中的join()方法。

    b. 后台线程

    后台线程是在后台运行,为其他线程提供服务。特征是,如果前台进程死亡,它也随之死亡。调用Thread对象的setDaemon(true)方法可以将其设置为后台进程。前台线程创建的子线程默认为前台线程,后台线程创建的子线程默认为后台线程。为保证正确的设置setDaemon(true)方法必须在start()之前调用。

    c. 线程睡眠sleep

    Thread类所提供的sleep方法可以是当前执行的线程休眠处于阻塞状态一段规定的时间,即使系统中没有其他可运行的线程,它也不会被调用。对其他的线程没有优先级的要求,都可以获得处理器资源。

    d. 线程让步yield

    Thread类的静态方法yield是当前运行的线程转入就绪状态,并不会阻塞该线程,只是让系统的线程调度器重新调度一次

 5. 线程的同步

    多线程的安全问题来自于当不同线程切换时,对同一数据的操作引起的混乱。例如两个线程并发同时对同一个文件修改时就有可能造成异常。对于线程的同步,Java提供了如下几种方式解决:

    a. 同步代码块

    Java的多线程支持引入同步监视器来解决这个问题。

 

  1. synchronized(obj){  //括号中的obj就是同步监视器
  2.  
  3.      ...  
  4.      //此处的代码就是同步代码块  
  5. //上述代码的含义就是,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。通常推荐使用可能被并发访问的共享资源充当同步监视器。

    这样一来,任何想要修改制定资源的资源执行时,首先说该资源加锁,在加锁期间其它进程无法修改该资源,而在其修改完成之后便释放对该资源的锁定。从而使得同一时刻只有一条线程处于临界区内,保证了安全。

    需要注意的是,同步监视器一般可以由任何对象充当,只要其唯一恒定就可以,通常推荐使用可能被并发访问的共享资源充当同步监视器。

    b. 同步方法

    在Java中的多线程中,使用synchronized关键字修饰的方法成为同步方法。对于同步方法,就无需指定监视器,该对象本身就是监视器。在同步方法执行的时候,就会首先锁定同步监视器this,synchronized保证了只有一条线程对资源的访问。所以,应该把同步方法定义在需要被独占访问的对象类内部。

  c. 同步锁

    同步锁是线程同步的另一种机制。使用被声明为final的同步锁作为同步监视器,在方法体执行前首先显式加锁,执行完后在显式的解锁,由于使用Lock对象时每个Lock对象都对应一个被访问的对象,其实也就等同于同步方法的实现机制,保证了线程的同步。

 

 
 

  1. class X{90 
  2. //定义锁对象  
  3. private final ReentrantLook lock=new ReentrantLook();  
  4. //...  
  5. //定义需要保证线程安全的方法  
  6. public void m(){  
  7. //加锁  
  8. lock.lock();  
  9. try{  
  10. //方法体  
  11. //.....  
  12. }finally{  
  13. //解锁  
  14. lock.unlock();  
  15.       }  
  16.   }   

    

    以上的各种线程同步机制,其实就是以一个始终恒定不变的对象作为同步监视器。在同步块代码中,虽然方法定义在线程类的内部,但是作为同步监视器的被访问对象是在初始化之后从外部传递到其内部的,具有不变性。而在同步方法机制中,同步监视器就是this对象,当对象初始化时,已经恒定了对象,所以其同步监视器也是不变的。Lock机制和同步方法的机制类似,只不过其监视器是lock,因为当初始化独占访问对象时也会初始化lock,所以也是恒定的。

  6. 线程通信

  a. 控制线程协调运行的机制(1)

    Java中有内置的三种方法对线程进行协调控制,他们属于Object类,但调用的时候要使用同步监视器来调用。

    wait()方法使当前线程等待,释放对该同步器的锁定。

    notify()方法唤醒该同步器上等待的单个线程,对于唤醒的选择是任意的。

    notify()唤醒全部等待的线程。

  b. 控制线程协调运行的机制(2)

    在使用了Lock对象保证同步的的程序中,同步监视器并非this对象本身,就要使用Condition对象的的方法保证同步。   

 

 
 

  1. //显式的获得Lock对象  
  2. private final Lock lock=new() ReentrantLock();  
  3. //获得指定Lock对象的条件变量  
  4. private final Condition cond=lock.newCondition();  
  5. //.....  
  6. cond.await();  
  7. //.....  
  8. cond.signal();  
  9. //.....  
  10. cond.signalAll();  

    这三个方法的功能同上面的三个方法功能。

  c. 使用管道流进行通信

    同I/O流中的机制(在I/O学习之后补上该部分)

  7. 线程池

    线程池在系统启动时即创建大量线程,程序将一个Runnable或者Callable对象传给一个线程池,线程池就会启动一条线程执行该对象的run方法,当run方法执行结束后,该线程不会死亡,而是重新返回线程池成为空闲状态。

 

 
 

  1. //实现Runnable接口来定义一个简单的  
  2. class TestThread implements Runnable  
  3. {  
  4.     public void run()  
  5.     {  
  6.         for (int i = 0; i < 100 ; i++ )  
  7.         {  
  8.             System.out.println(Thread.currentThread().getName()  
  9.                 + "的i值为:" + i);  
  10.         }  
  11.     }  
  12. }  
  13.  
  14. public class ThreadPoolTest  
  15. {  
  16.     public static void main(String[] args)   
  17.     {  
  18.         //创建一个具有固定线程数(6)的线程池  
  19.         ExecutorService pool = Executors.newFixedThreadPool(6);  
  20.         //向线程池中提交2个线程  
  21.         pool.submit(new TestThread());  
  22.         pool.submit(new TestThread());  
  23.         //关闭线程池  
  24.         pool.shutdown();  
  25.     }  
  26. }  

posted on 2011-06-23 15:11  小小博客小小员  阅读(469)  评论(0编辑  收藏  举报

导航