Java 多线程、并发系列之线程定义和线程状态

一、定义线程

1、扩展java.lang.Thread类。

此类中有个run()方法,应该注意其用法:
public void  run()

java.lang
类 Thread
java.lang.Object
     java.lang.Thread

所有已实现的接口:
Runnable

public class  Thread  extends Object implements Runnable

如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。Thread 的子类应该重写该方法。

创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。例如:

public  class  TestThread  extends  Thread {
         public  TestThread(String name) {
                super (name);
           }

            public  void  run() {
                for ( int  i = 0;i<5;i++){
                    for ( long  k= 0; k <100000000;k++);
                   System.  out .println( this  .getName()+ " :" +i);
               }
           }

            public  static  void  main(String[] args) {
               Thread t1 =  new  TestThread( "阿三"  );
               Thread t2 =  new  TestThread( "李四"  );
               t1.start();
               t2.start();
           }

}
在调用start()方法之后:发生了一系列复杂的事情

启动新的执行线程(具有新的调用栈);

该线程从新状态转移到可运行状态;

当该线程获得机会执行时,其目标run()方法将运行。

2、实现java.lang.Runnable接口。

public  class  ThreadTest   implements  Runnable{

         private  String  name  ;
       
         public  ThreadTest(String name)
       {
                this . name  =name;
       }
       
       
         @Override
         public  void  run() {
                //  TODO  Auto-generated method stub
              
                for ( int  i=0;i<5;i++)
              {
                        for  ( long  k = 0; k < 100000000; k++) ;
                   System.  out .println( name  +  ": "  + i);
              }
              
              
       }
       
         public  static  void  main(String args[])
       {
              ThreadTest test=  new  ThreadTest( "Fish"  );
              ThreadTest test2=  new  ThreadTest( "Bigfish"  );
              
              Thread testThread=  new  Thread(test);
              Thread testThread2=  new  Thread(test2);
              
              testThread.start();
              testThread2.start();
       }
       

线程的调度是JVM的一部分,在一个CPU的机器上上,实际上一次只能运行一个线程。一次只有一个线程栈执行。JVM线程调度程序决定实际运行哪个处于可运行状态的线程。

众多可运行线程中的某一个会被选中做为当前线程。可运行线程被选择运行的顺序是没有保障的。

尽管通常采用队列形式,但这是没有保障的。队列形式是指当一个线程完成“一轮”时,它移到可运行队列的尾部等待,直到它最终排队到该队列的前端为止,它才能被再次选中。事实上,我们把它称为可运行池而不是一个可运行队列,目的是帮助认识线程并不都是以某种有保障的顺序排列唱呢个一个队列的事实。

尽管我们没有无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。

二、线程的状态

线程有五种基本状态:新生状态,就绪状态,运行状态,阻塞状态,死亡状态。状态间关系如下图:



1、新状态:线程对象已经创建,还没有在其上调用start()方法。

 

2、可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。

 

3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

 

4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。

 

5、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。


    线程的睡眠方法
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。当线程睡眠时,它入睡在某个地方,在苏醒之前不会返回到可运行状态。当睡眠时间到期,则返回到可运行状态。

睡眠的实现:调用静态方法。
        try {
            Thread.sleep(123);
        } catch (InterruptedException e) {
            e.printStackTrace();  
        }
 
睡眠的位置:为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程中会睡眠。

使用睡眠方法应该注意:
1、线程睡眠是帮助所有线程获得运行机会的最好方法。
2、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
3、sleep()是静态方法,只能控制当前正在运行的线程。

线程的优先级

public static void yield()
  暂停当前正在执行的线程对象,并执行其他线程。 

线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。
 
注意:当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作。

Thread接口中的优先级 摘自JDK1.6

字段摘要
static intMAX_PRIORITY
          线程可以具有的最高优先级。
static intMIN_PRIORITY
          线程可以具有的最低优先级。
static intNORM_PRIORITY
          分配给线程的默认优先级。

源码中的定义
    public  final  static  int  MIN_PRIORITY  = 1;
     public  final  static  int  NORM_PRIORITY  = 5;
     public  final  static  int  MAX_PRIORITY  = 10;
默认的优先级为 public   final   static  int   NORM_PRIORITY  = 5;

当一个父线程生成一个子线程时,子线程的优先级为父线程的,在源码中:
init()方法中有这么一句:

 Thread parent = currentThread();
   this .  priority  = parent.getPriority();
Java程序的主线程(main方法)的优先级默认是为NORM_PRIORITY,那么后序创建的线程优先级就默认为5了

如何设置线程优先级:

Threa类源码中,有一个   setPriority  方法,源码如下:   final方法,无法被子类覆盖
优先级不能超出1-10的取值范围,否则抛出IllegalArgumentException。另外如果该线程已经属于一个线程组(ThreadGroup),该线程的优先级不能超过该线程组的优先级:
public  final  void  setPriority ( int  newPriority) {
        ThreadGroup g;
        checkAccess();
         if  (newPriority >  MAX_PRIORITY  || newPriority <  MIN_PRIORITY ) { 
             throw  new  IllegalArgumentException();
        }
         if ((g = getThreadGroup()) !=  null ) { //得到线程组的优先级,并且不能大于线程组的优先级
             if  (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(  priority  = newPriority);  // setPriority0  为一个本地方法
        }
    }
  private  native  void  setPriority0 ( int  newPriority);

线程组的优先级问题
  在Java中每个线程都属于某个线程组(ThreadGroup)。  以使用下面的指令来获得目前线程所属的线程组名称:
Thread.currentThread().getThreadGroup().getName();
  每一个线程产生时,都会被归入某个线程组,视线程是在哪个线程组中产生而定。如果没有指定,则归入产生该子线程的线程的线程组中。

查看线程组源码


  private  ThreadGroup() {      // called from C code
         this . name  =  "system"  ;
         this . maxPriority  = Thread. MAX_PRIORITY ;
         this . parent  =  null ;
    }
  public  final  int  getMaxPriority() {
         return  maxPriority  ;
    }

同样还有一个 setMaxPriority 方法
  public  final  void  setMaxPriority( int  pri) {
         int  ngroupsSnapshot;
        ThreadGroup[] groupsSnapshot;
         synchronized  ( this  ) {
            checkAccess();
             if  (pri < Thread. MIN_PRIORITY  || pri > Thread. MAX_PRIORITY ) {   //这个线程组的值必须要合法  [1,10]
                 return ;
            }
             maxPriority  = ( parent  !=  null ) ? Math.min(pri,  parent  . maxPriority  ) : pri; //这一句表示,当前设置的线程组优先级需要跟父线程的优先级最大级比较,最能取两个中的最小值,限制了set方法使得线程优先级大于父线程。如果没有父线程就用该设置值
            ngroupsSnapshot =  ngroups ;
             if  ( groups  !=  null ) {
                groupsSnapshot = Arrays. copyOf( groups , ngroupsSnapshot);
            }  else  {
                groupsSnapshot =  null ;
            }
        }
         for  ( int  i = 0 ; i < ngroupsSnapshot ; i++) {   //由于用户组的最大优先级变了,所有属于这个组的其他子线程的最大优先级也需要进行相应的改变,也就是说,仅仅能改变最大优先级,而不能改变已经创建子线程的实际优先级
            groupsSnapshot[i].setMaxPriority(pri);
        }
    }
线程组最大优先级总结:

线程组最大优先级的设定:

  • 系统线程组的最大优先级默认为Thread.MAX_PRIORITY
  • 创建线程组的时候其最大优先级默认为父线程组(如果未指定父线程组,则其父线程组默认为当前线程所属线程组)的最大优先级
  • 可以通过setMaxPriority更改最大优先级,但无法超过父线程组的最大优先级

setMaxPriority的问题:

  • 该方法只能更改本线程组及其子线程组(递归)的最大优先级。
  • 但不能影响已经创建的直接或间接属于该线程组的线程的优先级,也就是说,即使目前有一个子线程的优先级比新设定的线程组优先级大,也不会更改该子线程的优先级。只有当试图改变子线程的优先级或者创建新的子线程的时候,线程组的最大优先级才起作用。
线程的yield()方法

   public  static  native  void  yield();

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

yield()方法测试:
package  Threadtest;

public  class  yieldTest {
         public  static  void  main(String[] args) {  
                Thread t1 =  new  MyThread1();  
                Thread t2 =  new  Thread( new  MyRunnable());  
                t2.start();  
                t1.start();  
        }  
}  
class  MyThread1  extends  Thread {  
         public  void  run() {  
                 for  ( int  i = 0; i < 10; i++) {  
                        System.  out .println( "线程1第"  + i +  "次执行!"  );  
                }  
        }  
}  
class  MyRunnable  implements  Runnable {  
         public  void  run() {                  
                 for  ( int  i = 0; i < 10; i++) {                     
                   System.  out .println( "线程2第"  + i +  "次执行!"  );  
                        Thread. yield();
                }  
        }  
}
测试结果如下:
线程2第0次执行!
线程2第1次执行!
线程2第2次执行!
线程2第3次执行!
线程1第0次执行!
线程1第1次执行!
线程1第2次执行!
线程1第3次执行!
线程1第4次执行!
线程1第5次执行!
线程1第6次执行!
线程1第7次执行!
线程1第8次执行!
线程1第9次执行!
线程2第4次执行!
线程2第5次执行!
线程2第6次执行!
线程2第7次执行!
线程2第8次执行!
线程2第9次执行!

线程的join方法()

用途:
主线程生成并起动了子线程,而子线程里要进行大量的耗时的运算 ( 这里可以借鉴下线程的作用 ) ,当主线程处理完其他的事务后,需要用到子线程的处理结果,这个时候就要用到 join(); 方法了。

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
举个例子:
A线程为主线程 main
B线程为main中生成的子线程

main(
       B.start();
        B.join();
        
        sysout(n);//n为B中处理的数据,很明显  ,sysout为main()线程的东西,所以调用sysout时,需要B处理完才调用sysout



查看jdk中源码

public  final  void  join ()  throws  InterruptedException {  //默认的是join(0)表示等待时间为0
        join(0);
    }
/** 
      Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
永远等待,直达t结束*/
  public  final  synchronized  void  join ( long  millis)        //join的内部细节是用的wait方法  Object 提供的方法
     throws  InterruptedException {
         long  base = System.currentTimeMillis();
         long  now = 0;
         if  (millis < 0) {
             throw  new  IllegalArgumentException( "timeout value is negative" );
        }
         if  (millis == 0) {
             while  (isAlive()) {  //线程启动了 join地方法才有用,即需要在start()方法后面调用 join()
                wait(0);
            }
        }  else  {
             while  (isAlive()) {
                 long  delay = millis - now;
                 if  (delay <= 0) {
                     break ;
                }
                wait(delay);
                now = System. currentTimeMillis() - base;
            }
        }
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值