论坛首页 Java企业应用论坛

更好的把握线程<一>:Thread (线程)介绍

浏览 12178 次
精华帖 (0) :: 良好帖 (2) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-06-12   最后修改:2008-12-30
OO
线程(引用自Java API doc):
引用
线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。


线程的一些特性:
  • 所有的Java代码都是在某个线程中执行的,所以在任一行Java代码中使用Thread.currentThread()都可以得到当前运行线程。
  • JVM允许多个线程并发执行,虽然同一时刻只能有一个线程占用CPU,但每个线程占有的时间片非常短,所以人类的感官上多个线程是并发执行的。
  • 当 JVM启动时,至少有一个用户线程运行,即执行某个类的main方法的线程。


线程在下列情况之一终止运行:
  • Thread.run方法运行完毕(或者是Thread包含的Runnable对象的run方法执行完毕)
  • run方法内的代码运行时发生异常。


JVM在下列情况下终止运行:
  • 所有非守护线程(即用户线程,非daemon线程)终止。假如有main线程和另外一个用户线程在运行,即使main线程终止了,还必须等待另外一个线程终止JVM才会终止。
  • 调用了Runtime类的exit方法(启动虚拟机的关闭序列)。
  • 外部程序强行终止虚拟机执行(非正常退出)。


创建线程:
[list]
  • 继承Thread类,覆盖run方法
  • 实现Runnable接口,通过Thread类的带Runnable参数的构造函数传入到Thread对象内,一种常用的方式:
  • Thread t=new Thread(new Runable(){
      public void run(){
         //do something
      } 
    });
    

    [/list]
    运行线程:
    • 调用Thread的start方法,调用之后,JVM启动一个新的线程,在新线程中执行该线程对象的run方法。
    • 线程启动之后,不能再调用start方法,否则会抛出IllegalThreadStateException

    线程的一些自动继承的特性:
    • 如果未指明优先级,则被创建的线程和创建它的线程具有相同优先级。
    • 如果未指明ThreadGroup,则被创建的线程和创建它的线程使用相同的ThreadGroup。如果指定ThreadGroup为null,则系统会自动将本线程加入系统级的ThreadGroup。所以说不存在没有ThreadGroup的线程。
    • 如果未指明是否守护线程,则被创建的线程和创建它的线程具有相同的daemon属性。也就是说守护线程创建的线程如果未特别指定,则是守护线程,用户线程创建的线程如果未特别指定,则是用户线程。main线程是用户线程,除非被Thread.currentThread().setDaemon(true)方式改变。(根据oxromantic的指正,此处是错误的,正在运行的线程如果调用setDaemon会抛出java.lang.IllegalThreadStateException异常,main线程肯定是正在运行的,setDaemon只有在start()方法之前调用才有效。感谢oxromantic指正)
    守护线程和用户线程:
    [list]
  • 当最后一个用户线程终止的时候,所有守护线程将被“终止”(不是被interrupt),虚拟机退出。即使守护线程是个死循环。以下代码放入main方法里:
  • //请等待足够久的时间(可能是1-2分钟),程序会自动停止。
    Thread t1=new Thread(){
     public void run(){
      int i=0;
      while(true){//死循环
       i++;
       System.out.println(i);
       //Thread.yield();//如果想让t2有机会更快完成,需要调用yield让出CPU时间。
      }
     }
    };
    t1.setDaemon(true);//注释掉这句就可以看出区别了。
    Thread t2=new Thread(){
     public void run(){
      int i=50000;
      while(i>0){
       i--;
      }
      System.out.println("t2 done");
     }
    };
    t1.start();
    t2.start();
    

    [/list]
    线程优先级:
    • 线程优先级范围是1-10,1最低优先级10最高优先级。
    • 优先级越高越先被JVM从“等待运行”(waiting-to-run)的状态挑选出来运行。
    • JVM线程和操作系统线程的关系有2种(甚至可能是3种,依赖于平台和JVM实现):
    •   [list=1]
       
    • n-1关系,所有JVM线程都在同一个OS线程中运行。
    •  
    • n-m关系,一个JVM线程是可以在多个OS线程中运行,一个OS线程可运行多个JVM线程,不管硬件是多核还是单核。
    •  
    • 1-1关系,一个JVM线程对应一条OS进程。(早期JVM的在Linux上的实现版本)
    •  

    所以,线程的优先级设置是不可靠的,依赖于JVM实现和OS。.NET线程5个等级:Highest,AboveNormal,Normal,BelowNormal,Lowest.Solaris有15个等级,最高的5个等级是给进程中断层级(Process Interrupt Levels ,PILs)使用。也就是说PIL可以中断普通进程知道执行直到其运行结束。剩下的10个优先级可以被线程使用。所以优先级8和9可能并没有区别。默认优先级是5,最大优先级由所在ThreadGroup的maxPriority决定。也就是说所属ThreadGroup最大优先级是8则,以构造函数Thread(ThreadGroup g,String name)所创建的线程即使将其设置为10,优先级仍然是8.
  • 当等待运行的线程中有3种优先级相差比较大的线程在运行的时候,单任务操作系统会按高-中-低的顺序来执行线程,也就是说先跑完所有高等级的再跑低等级的,多任务操作系统则会间歇执行,但不保证高优先级的、相同任务内容的任务会更快完成,即使你让线程跑上10分钟。非常诡异,可能跟CPU是双核有关(但用3个优先级且混合启动线程还是有可能高优先级的跑输给低优先级的,哪怕是最高10和最低1)。
  • 给出代码,请读者分别在Linux和Windows下测试,有条件的话在Solaris上也试试。给个报告出来。
    	public static void main(String[] args) throws Exception {
    		int x=211;//210个线程分3组,每组70个,第211是“发令枪”
    		int n=0;//a类线程数量,本类线程优先级最高
    		int m=0;//b类线程数量,本类线程优先级最低
    		int l=0;//c类线程数量,本类线程优先级中间(5)
    		
    		final CyclicBarrier cb=new CyclicBarrier(x);//发令枪类,让所有线程理论上在同一起跑线(也许和逐个逐个start没区别)
    		final List ln=new ArrayList(7);//存放a类线程的列表,为了在匿名类中可见,定义为final,7原本是21个线程在跑,后来为了理论上消除误差,增加到70个,此处没有修改过来。
    		final List lm=new ArrayList(7);//存放b类线程的列表
    		final List ll=new ArrayList(7);//存放c类线程的列表
    		for(int i=0;i<x-1;i++){//为了避免线程在创建的时候同类线程扎堆(理论上可能)产生误差,打乱创建过程。为了避免2组线程分别在双核CPU上各自单核上运行,采用了3组线程。
    			Runner t=null;
    			if(i%3==0){
    				t=new Runner(cb,"a"+i);
    				t.setPriority(8);//可以用10,但测试结果看不出区别。
    				ln.add(t);
    				n++;
    			}else if(i%3==1){
    				t=new Runner(cb,"b"+i);
    				t.setPriority(2);//可以用1,
    				lm.add(t);
    				m++;
    			}else{
    				t=new Runner(cb,"c"+i);
    				t.setPriority(5);
    				ll.add(t);
    				l++;
    			}
    			t.start();//不是真的启动线程哦。请看Runner类
    		}
    		System.out.println(n);//检验是不是每组70个。
    		System.out.println(m);
    		System.out.println(l);
    		
    		try {
    			Thread.sleep(3000);//本意是为了让Runner在起跑线都预备好,在此停3秒。减少误差。
    			Timer timer =new Timer(true);//定时打印结果的线程。
    			timer.scheduleAtFixedRate(new TimerTask(){
    
    				@Override
    				public void run() {//定时打印每组线程的结果的平均值,由于打印是有先后顺序的,所以使用平均值,消除时间差(用心良苦啊。。。)
    					System.out.println("a avg--"+getAvg(ln));//可以将a组的结果放在最后,结果通常还是a最慢
    					System.out.println("b avg--"+getAvg(lm));
    					System.out.println("c avg--"+getAvg(ll));
    				}
    				
    				
    			}, 3000, 3000);//3秒打印一次。
    			cb.await();//发令枪响。
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		} catch (BrokenBarrierException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		System.out.println("started ");
    		
    		
    	}
    	
    	
    	public static BigInteger getAvg(List l){//为什么用大整数?因为long,和int我都试过得不到想要的结果。
    		BigInteger total=BigInteger.valueOf(0);
    		for (Iterator iter = l.iterator(); iter.hasNext();) {
    			Runner r = (Runner) iter.next();
    			total=total.add(r.getCountPerMs());
    		}
    		return total.divide(BigInteger.valueOf(l.size()));//同组线程的结果的平均值
    	}
    	
    	static class Runner extends Thread{//有心人可以试试改成只实现Runnable接口
    		private CyclicBarrier cb;
    		private String name;
    		private BigInteger count=BigInteger.valueOf(0);
    		private long startTime;
    		public Runner(CyclicBarrier cb,String name){
    			this.cb=cb;
    			this.name=name;
    		}
    		public void run(){
    			try {
    				cb.await();//让其在起跑线上等待其他线程和发令枪声
    				System.out.println(this.name+"start");//看看各个线程真正跑起来是否一致,不怕,每组有70条,一条拖后退问题不大,只要运行时间够长,体力好的应该还是不会输在起跑线的。
    				
    				startTime=System.currentTimeMillis();//原本是为了消除时间差使用的,效果不好,改用发令枪,留着成了僵尸代码
    				for (;;) {
    					count=count.add(BigInteger.valueOf(1));
    					Thread.sleep(1);//调试手段,为了使线程慢跑,可以去掉对比结果。
    //					if(count%10000==0){
    //						Thread.yield();//调试手段,效果不明显,也毙掉了。
    //					}
    //					if(count.mod(m)==0){
    //						String info = name+":"+(count/100000)+"--"+this.getPriority()+"-"+this.isDaemon();
    ////						System.out.println(info);//早期调试手段,毙掉。
    //					}
    				}
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			} catch (BrokenBarrierException e) {
    				e.printStackTrace();
    			}
    			 
    		}
    		public BigInteger getCountPerMs(){
    //			long value=System.currentTimeMillis()-startTime;
    ////			System.out.println(value);
    ////			System.out.println("count "+this.count);
    //			if(value==0)return BigInteger.valueOf(0);
    //			return this.count.divide(BigInteger.valueOf(value));
    //以上一大段注释掉的代码原本是为了消除时间差的。没啥效果,毙掉
    			return this.count;
    		}
    	}
    
    

    结果大大出乎意料,通常a跑得最慢,b跑得最快.......
    理论估计:会不会是a优先级高切换太频繁,切换的开销大过运行的开销??,猜测,高手可以指点一下。
    结论:不要依赖线程之间的竞争来得到想要的结果。
    [/list]
    先写到这,下一节开讲线程的使用技巧和设计思想。

       发表时间:2008-06-12  
    把A组放在后面统计,我的运行结果如下,A组反而落后于B组
    70
    70
    70
    b16start
    c17start
    a18start
    c20start
    b19start
    a21start
    b22start
    c23start
    a24start
    b25start
    c26start
    a27start
    b28start
    c29start
    a30start
    b31start
    c32start
    a33start
    b34start
    c35start
    a36start
    b37start
    c38start
    a39start
    b40start
    c41start
    a42start
    b43start
    c44start
    a45start
    b46start
    c47start
    a48start
    b49start
    c50start
    a51start
    b52start
    a54start
    b55start
    c56start
    a57start
    b58start
    c59start
    a60start
    b61start
    c62start
    a63start
    b64start
    a66start
    c65start
    b67start
    c68start
    a69start
    b70start
    c71start
    a72start
    b73start
    c74start
    a75start
    b76start
    c77start
    a78start
    b79start
    c80start
    a81start
    b82start
    c83start
    a84start
    b85start
    c86start
    a87start
    b88start
    c89start
    a90start
    b91start
    c92start
    a93start
    b94start
    c95start
    a96start
    b97start
    c98start
    a99start
    b100start
    a15start
    c53start
    c101start
    b103start
    a105start
    c107start
    b109start
    a111start
    c113start
    b115start
    a117start
    c119start
    b121start
    b124start
    a123start
    a126start
    c125start
    c128start
    b127start
    b130start
    a129start
    a132start
    c131start
    b133start
    a135start
    c137start
    b139start
    c140start
    a141start
    b142start
    c143start
    a144start
    b145start
    c146start
    a147start
    b148start
    c149start
    a150start
    b151start
    c152start
    a153start
    b154start
    c155start
    a156start
    b157start
    c158start
    a159start
    b160start
    c161start
    a162start
    c134start
    b163start
    c164start
    a165start
    b166start
    c167start
    a168start
    b169start
    c170start
    a171start
    c173start
    b175start
    c176start
    a177start
    c179start
    b181start
    c182start
    a183start
    c185start
    a186start
    b187start
    c188start
    a189start
    b190start
    c191start
    a192start
    b193start
    a195start
    b196start
    c197start
    a198start
    b199start
    c200start
    a201start
    b202start
    c203start
    a204start
    b205start
    c206start
    a207start
    c209start
    a108start
    b112start
    a114start
    c116start
    b118start
    a120start
    c122start
    b136start
    a138start
    b172start
    b178start
    a180start
    b184start
    b208start
    started 
    b1start
    a0start
    c2start
    a3start
    b4start
    c5start
    a6start
    b7start
    c8start
    a9start
    b10start
    c11start
    a12start
    b13start
    c14start
    c194start
    a174start
    a102start
    c104start
    b106start
    c110start
    b avg--94766
    c avg--94174
    a avg--115073
    b avg--224373
    c avg--216470
    a avg--207931
    b avg--341876
    c avg--344409
    a avg--332709
    b avg--466582
    c avg--466450
    a avg--448567
    b avg--659035
    c avg--653320
    a avg--638056
    b avg--784288
    c avg--776086
    a avg--770561
    b avg--908349
    c avg--898104
    a avg--894808
    b avg--1021412
    c avg--1026939
    a avg--1015154
    b avg--1141776
    c avg--1147952
    a avg--1131192
    b avg--1266781
    c avg--1270811
    a avg--1246034
    0 请登录后投票
       发表时间:2008-06-12  
    本人INTEL T2050 CPU,双核。系统是WindowsXP
    0 请登录后投票
       发表时间:2008-12-30  
    这种自己不认真验证的文章真是误人子弟
    Thread.currentThread().setDaemon(true)
    你倒是设给我看看

    引用
        /**
         * Marks this thread as either a daemon thread or a user thread. The
         * Java Virtual Machine exits when the only threads running are all
         * daemon threads.
         * <p>
         * This method must be called before the thread is started.
          * <p>
         * This method first calls the <code>checkAccess</code> method
         * of this thread
         * with no arguments. This may result in throwing a
         * <code>SecurityException </code>(in the current thread).
        *
         * @param      on   if <code>true</code>, marks this thread as a
         *                  daemon thread.
         * @exception  IllegalThreadStateException  if this thread is active.
         * @exception  SecurityException  if the current thread cannot modify
         *               this thread.
         * @see        #isDaemon()
         * @see        #checkAccess
         */
        public final void setDaemon(boolean on)
    1 请登录后投票
       发表时间:2008-12-30  
    oxromantic 写道
    这种自己不认真验证的文章真是误人子弟
    Thread.currentThread().setDaemon(true)
    你倒是设给我看看

    引用
        /**
         * Marks this thread as either a daemon thread or a user thread. The
         * Java Virtual Machine exits when the only threads running are all
         * daemon threads.
         * <p>
         * This method must be called before the thread is started.
          * <p>
         * This method first calls the <code>checkAccess</code> method
         * of this thread
         * with no arguments. This may result in throwing a
         * <code>SecurityException </code>(in the current thread).
        *
         * @param      on   if <code>true</code>, marks this thread as a
         *                  daemon thread.
         * @exception  IllegalThreadStateException  if this thread is active.
         * @exception  SecurityException  if the current thread cannot modify
         *               this thread.
         * @see        #isDaemon()
         * @see        #checkAccess
         */
        public final void setDaemon(boolean on)

    谢谢你的指正。的确是无法修改已启动的用户线程的为守护线程。感谢你那么仔细的阅读拙作
    0 请登录后投票
    论坛首页 Java企业应用版

    跳转论坛:
    Global site tag (gtag.js) - Google Analytics