J2SE快速进阶——Java多线程机制

学习Java中的线程时,自然而然地联想到之前学过的操作系统中处理器那一块的知识。

   定义

       文章开头,先大概说一下程序、进程和线程的概念及其之间的关系。

       程序:程序就是一段静态的代码,或者一个可执行程序。

       进程:进程是程序的一次动态执行的过程,它对应着从代码加载、运行到结束的一次动态的执行过程。

       线程:比进程更小的执行单位,一个进程在执行过程中,可以产生多个线程,也就是多个分支。它是程序执行流的最小单位。

       来看一个小程序:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         fun1();  
  4.     }  
  5.     public static void fun1(){  
  6.         System.out.println(fun2()+fun3());        
  7.     }  
  8.     public static String fun2(){  
  9.         return "Hello ";  
  10.     }  
  11.     public static String fun3(){  
  12.         return "World!";  
  13.     }  
  14. }  

          它的执行顺序如下,在main方法中,从①执行到⑥是一条线,并没有分支,这就是一个线程。

         

   

        线程的实现

        当JVM执行到main方法时,就会启动一个线程,叫做主线程。如果main方法中还创建了其他线程,那么JVM就会在主线程和其他线程之间轮流切换,保证每个线程都有机会使用CPU资源。

       Java中的线程是通过java.lang.Thread类来实现的,每一个Thread对象都代表一个新的线程。


       Java中实现线程有两种方法:

       1、继承Thread类,并且重写其run方法(用来封装整个线程要执行的命令),调用start方法启动线程。

       比如下面这个类T要实现线程,则代码如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class CreateThreadTest{  
  2.     public static void main(String[] args) {  
  3.         T r=new T();  
  4.         r.start();   //T类的线程开始执行  
  5.         for(int i=0;i<100;i++)  
  6.         {  
  7.             System.out.println("主线程正在执行~~~~"+i);  
  8.         }  
  9.     }  
  10. }  
  11. class T extends Thread  
  12. {  
  13.     public void run(){//重写父类中的run方法  
  14.         for(int i=0;i<100;i++)  
  15.         {  
  16.             System.out.println("我创建的线程正在执行~~~~"+i);  
  17.         }  
  18.     }  
  19. }  
       现在,这一个小程序一共有两个线程正在执行,一个是主线程,还有一个是T类创建的线程。

        

        2、实现Runnable接口

        还有一种方法就是让实现线程的类实现Runnable接口,实现Runnable接口中唯一的方法run(),然后把此类的实例当做Thread类的构造函数的参数,创建线程对象。

        例如上面的例子,还可以这样写:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class CreateThreadTest {  
  2.     public static void main(String[] args) {  
  3.         T r=new T();  
  4.         Thread t=new Thread(r);    //创建线程对象  
  5.         t.start();    //T类的线程开始  
  6.         for(int i=0;i<100;i++)  
  7.         {  
  8.             System.out.println("主线程正在执行~~~~"+i);  
  9.         }  
  10.     }  
  11. }  
  12. class T implements Runnable   
  13. {  
  14.     public void run(){  
  15.         for(int i=0;i<100;i++)  
  16.         {  
  17.             System.out.println("我创建的线程正在执行~~~~"+i);  
  18.         }  
  19.     }  
  20. }  

       两种方法的实质就是,都需要重写run方法,最终都是由Thread类的start方法启动线程。

       温馨提示:因为Java中不支持多继承,所以实现线程时,一旦继承了Thread类,就无法再继承其他类了。但Java支持实现多个接口,所以推荐采用第二中方法,比较灵活。


       多线程

       多线程主要是为了同步完成多项任务,即同时执行多个线程。多线程把一个进程划分为多个任务,它们彼此独立地工作

       我们都知道,现在大部分操作系统比如Windows、Mac OS X、Unix、Linux等,都是支持多线程的,但我们平时所说的多线程,并不意味着CPU在同时会处理多个线程,每个CPU在同一个时间点只会处理一个线程,只不过速度太快了,处理的时间极短,以至于我们可以认为它是在同一个时间段可以处理多个线程。所以只有当你的机器是“双核”甚至“多核”时,才能实现真正意义上的多线程。                                                                                            

         

       下面看一个多线程的例子:

        两个线程t1和t2的执行        

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class ThreadTest {  
  2.     public static void main(String[] args) {  
  3.         Thread t1=new Thread(new T1());  
  4.         Thread t2=new Thread(new T2());  
  5.         t1.start();t2.start();  
  6.     }     
  7. }  
  8. class T1 implements Runnable{  
  9.     public void run(){  
  10.         for(int i=0;i<10;i++){  
  11.             System.out.println("线程1正在执行中------"+i);  
  12.             if(i==9){  
  13.                 System.out.println("线程1执行已结束------"+i);  
  14.                 break;  
  15.             }  
  16.         }  
  17.     }  
  18. }  
  19. class T2 implements Runnable{  
  20.     public void run(){  
  21.         for(int i=0;i<10;i++){  
  22.             System.out.println("线程2正在执行中------"+i);  
  23.             if(i==9){  
  24.                 System.out.println("线程2执行已结束------"+i);  
  25.                 break;  
  26.             }  
  27.         }  
  28.     }  
  29. }  
         看下面的结果之前,先充分发挥你的大脑想一想到底结果应该是什么样子的~~

         执行结果:

          

       是不是跟您预测的不一样呢?如果不加线程的话,本来的结果应该前十行都是线程1在执行,线程1执行完后线程2才开始执行,线程调度算法使得每个线程执行一会进入等待状态,再去执行另一个线程。

         

       线程中常用的方法

       如果亲手尝试过上面这个例子中,会发现这两个线程t1和t2谁先执行,谁后执行,谁执行多长时间等等这些都是不确定、不可控的。下面就说一下线程中常用到的几个方法。

       ★ void yield()方法

       在一个线程中,如果执行到yield()方法,那么这个线程就会让出CPU资源,暂停当前正在执行的线程对象,转而让CPU去执行其他具有相同优先级的线程。充分体现了线程乐于谦让的精神!

       例:i的值从0到99,每次输出线程实例的名字,当i是10的倍数时,执行yield方法。 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.  public class TestYield {  
  2.     public static void main(String[] args) {  
  3.         MyThread thread1=new MyThread("thread1");  
  4.         MyThread thread2=new MyThread("thread2");  
  5.         thread1.start();thread2.start();  
  6.     }  
  7. }  
  8. class MyThread extends Thread{  
  9.     MyThread(String name){  
  10.         super(name);  
  11.     }  
  12.     public void run(){  
  13.         for(int i=0;i<100;i++){  
  14.             System.out.println(getName()+":"+i);  
  15.             if(i%10==0){  
  16.                 yield();  
  17.             }  
  18.         }  
  19.     }  
  20. }  
       从结果中的前几条输出可以发现,对thread1来说,每当i是10的倍数时,进程就会让出,thread2进程执行;thread2也是如此:

          

      注意: yield()方法会将当前运行的线程切换到可运行状态,但可能没有效果,因为实际中执行yield方法的线程还有可能被调度程序再次选中。

        

      ★ void sleep(long millis)方法和void sleep(long millis,int nanos)

       让线程休眠(暂停执行)指定的时间长度,参数millis的单位是毫秒,参数nanos的单位是纳秒。线程执行了sleep方法后就会进入阻塞状态,指定的时间段过后,线程进入可执行状态,等待被操作系统调度。

       例:      

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. import java.util.*;  
  2. public class TestSleep {  
  3.     public static void main(String[] args)  
  4.     {  
  5.         MyThread thread=new MyThread();  
  6.         thread.start();  
  7.     }  
  8. }  
  9. class MyThread extends Thread  
  10. {  
  11.     public void run()  
  12.     {  
  13.         while(true)  
  14.         {  
  15.             System.out.println("==="+new Date()+"===");  
  16.             try{  
  17.                 sleep(1000);  
  18.             }catch(InterruptedException e){  
  19.                 return;  
  20.             }  
  21.         }  
  22.     }  
  23. }  
        看完代码您应该猜到结果了,每隔一秒都会输出当前时间,相当于一个定时器。这个程序一共有两个线程,主线程(即main方法)中出了执行thread线程就没有其他任务需要执行,所以这里可以看做只执行thread这一个线程,如果把例子中的sleep方法去掉,那么就会“唰唰唰”地不断输出当前时间(你的CPU风扇也会“唰唰唰”~~~~)。

        

         ★void join()方法

         上面说线程执行了yield方法时,会自动让出CPU资源,使状态由运行状态转换为可运行状态。join()方法可以说是恰恰相仿,当一个线程执行了join方法时,那么它就会一直执行下去直到这个线程结束。

        还是举例来说明:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class TestJoin {    
  2.     public static void main(String[] args) throws InterruptedException {    
  3.         Thread t1 = new Thread(new ThreadA());    
  4.         Thread t2 = new Thread(new ThreadB());    
  5.         t1.start();    
  6.         t1.join(); // t1线程开始执行后,它将继续执行下去,直到t1线程结束,否则绝不让出CPU    
  7.         t2.start();    
  8.         t2.join(); // t2线程开始执行后,它将继续执行下去,直到t1线程结束,否则绝不让出CPU    
  9.     }    
  10. }  
  11. class ThreadA implements Runnable {     
  12.     public void run() {    
  13.         for (int i=0;i<10;i++) {    
  14.             System.out.println("A线程正在运行~~~~" + i);    
  15.         }     
  16.     }    
  17. }     
  18. class ThreadB implements Runnable {     
  19.     public void run() {    
  20.         for (int i=0;i<10;i++) {    
  21.             System.out.println("B线程正在运行~~~~" + i);    
  22.         }     
  23.     }     
  24. }   
       先来设想一下,加入t1和t2两个线程启动后不执行join方法,那么CPU给它们分配的执行时间和顺序就不一定相同,如左下图;如果t1和t2执行了join方法,那么它们一旦开始执行,就将执行到底,如右下图。

                  
 
      

        为什么要用多线程

        最后一个问题,为什么要用多线程?

        在论坛里看到一个大牛的比喻:单线程就是牛逼老板从头到尾一个人做完,另开一个线程就是老板掰出一件事情叫一个小弟去做,这个小弟的进入会加快整个事情的进展,但有时可能会做起事来碍手碍脚。


       当然,想要更深地理解多线程的精髓所在,光靠学这些理论还是不够的,更重要的还是在实践和项目中去挖掘和思考。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值