Java多线程

在java中其实即使我们没有定义多线程,java仍然不是一个单线程,例如,一个简单的java程序,仅仅是打印输出一条语句,它的主线程是从main方法开始执行,一直执行完main中的所有语句,完成main()方法后消亡,也许你会说,这就是一个单线程,但是,在jvm中还有一个不为人所知的线程在运行,在初学java时,可能就有人告诉你java会自动进行垃圾搜集,那么垃圾搜集是如何进行的呢?其实,就是通过一个线程来进行的。垃圾回收线程清除被废弃的对象,并回收它们占用的内存。因此,即使是一个只完成打印 “Hello, world”任务的Java程序也是运行在一个多线程的环境中,这两个线程便是主线程和垃圾回收线程。

       多线程的创建:

在java.lang包中,有一个Thread类用来代表线程对象,Thread类也是进行多线程编程时主要面对的类。java.lang包中的类不需引入便可以在程序中直接使用

1)如何获取当前线程对象

     无论对于主线程,还是主线程创建的子线程,都可以通过Thread类的currentThread()方法来获取当前线程对象,currentThread()方法是Thread类的静态方法,这意味着无须创建Thread对象便可以调用该方法。在获取线程对象之后,可以通过 Thread类的getId()方法获取该线程在整个Java虚拟机中唯一的标识。

以下的代码行先获取当前的线程对象,继而获取当前线程对象的标识:

System.out.println(Thread.currentThread().getId());

线程ID是一个正的长整型数,在创建该线程时生成。线程ID是唯一的,并在线程的生命周期内保持不变。线程被终止后,该线程ID可以被重新分配给其他的线程。

2)如何给线程命名

  线程的名称可以在创建线程时指定,如果创建线程时没有指定任何名称,Java虚拟机会以“Thread-序号”的规则为线程命名,例如“Thread-0”。主线程默认以“main”为线程的名称。线程的名称可以通过Thread类的getName()方法获得。

无论线程处于何种运行状态,均可以调用Thread类的setName()方法改变线程名称。主线程的名称也可以被改变。

以下的代码片段先打印主线程的名称,然后将其改变为“MyThread”:

System.out.println("线程的原名称是:"+Thread.currentThread().getName());

Thread.currentThread().setName("MyThread");

System.out.println("线程的新名称是:"+Thread.currentThread().getName());

运行效果如下:

线程的原名称是:main

线程的新名称是:MyThread

 

3)如何构造和启动线程

  构造线程对象是通过Thread类的构造方法完成的。Thread类的构造方法分为3类:独立构造方法、根据java.lang.Runnable对象的构造方法、根据线程组的构造方法。后面两类构造方法稍后介绍,下面介绍Thread类的独立构造方法:

—  Thread():构造一个线程,线程名由Java虚拟机根据线程命名规则指定。

—  Thread(String name):构造一个线程,以name为线程名。

下面代码片段构造了两个线程,其中thread1由Java虚拟机根据线程命名规则指定,thread2命名为“MyThread”:

Thread thread1=new Thread();

Thread thread2=new Thread("MyThread");

以上构建的线程都是没有任何运行逻辑的,也就是线程体为空。线程的线程体包含在Thread类的run()方法中,可以在构建线程对象时重载run()方法,写入自定义的逻辑。构建线程之后启动线程的方法是利用Thread类的start()方法。对Thread类的run()和start()方法说明如下。

—  void run():本方法中的逻辑为线程体。如果在构建线程时没有重载run()方法,则线程体为空;如果线程系根据传入的java.lang.Runnable对象而构建,则以该Runnable对象的run()方法为线程体。

—  void start():启动线程,开始执行线程对象的run()方法。

主线程一旦执行Thread对象的start()方法启动子线程,子线程的执行便和主线程形成了异步的关系,主线程不会等待子线程执行结束,而是立刻执行下面的语句。

4)如何设置线程优先级

  Java将线程的优先级分为10个等级,分别用1~10之间的数字表示。数字越大表明线程的优先级别越高。相应地,在Thread类中定义了表示线程最低、最高和普通优先级的成员变量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY,代表的优先级等级分别为1、10 和5。当一个线程对象被创建时,其默认的线程优先级是5。在创建线程对象之后可以调用线程对象的setPriority()方法改变该线程的运行优先级,同样可以调用getPriority()方法获取当前线程的优先级。

5)守护线程

  在Java中有一类被称为守护(Daemon)线程的,比较特殊的线程,这是一种优先级比较低的线程。守护线程具有最低的优先级,一般用于为系统中的其他对象和线程提供服务。将一个线程设置为守护线程的方式是在线程对象创建之前调用线程对象的setDaemon()方法。典型的守护线程例子是Java虚拟机中的垃圾自动回收线程,它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。可以通过调用线程对象的isDaemon()方法来判断某个线程是否是守护线程。

守护线程还有另一层含义:当创建守护线程的父线程终止时,作为子线程的守护线程也自动终止。反之,如果一个子线程不是守护线程,即使父线程终止了,它也不会终止。

当一个线程被创建时,它默认不是守护线程。

面的代码片段演示守护线程的用法。线程parent创建了一个子线程child,子线程child的循环体是一个无限循环,不断地打印“Child runs”字样。在child线程不是守护线程的情况下,即使parent线程终止了,child线程仍然在运行:

Thread parent=new Thread()

{

       public void run()

       {

             System.out.println("Parent starts");

             Thread child=new Thread()

             {

                   public void run()

                   {

                         System.out.println("Child starts");

                         while(true)

                         {

                               System.out.println("Child runs");

                         }

                   }

             };

             child.setDaemon(false);

             child.start();

             System.out.println("Parent ends");

       }

};

parent.start();

如果希望父线程终止时,子线程自动终止,只需要将“child.setDaemon(false);”改为“child.setDaemon(true);”即可。

6)           Runnable接口和Thread类

    在实际的应用开发中推荐采用的方式是:

— 将实现java.lang.Runnable接口的对象实例传递给java.lan.Thread类的构造方法。

— 专门设计一个类继承java.lang.Thread类。

java.lang.Runnable接口只定义了一个方法:public void run(),当实现该接口的对象被传递给java.lan.Thread类的构造方法时,该对象所实现的run()方法将成为新线程的线程体。

通过实现Runnable接口来构造线程体,进而构造线程对象的做法的突出优点是可以实现“单实例、多线程”。即当多个Thread对象是根据实现Runnable接口的对象来构造时,实现Runnable接口的对象实例只有一份,但是在这个对象上却运行着多个线程。

根据实现了Runnable接口的对象来创建线程的Thread类的构造方法如下。

—  Thread(Runnable target):根据target对象来创建线程,以target对象实现的run()方法作为线程体。线程名称由Java虚拟机指定。

—  Thread(Runnable target, String name):根据target对象来创建线程,以target对象实现的run()方法作为线程体。线程名称为name。

基于Runnable接口创建新线程的代码如下所示:

public class RunnableImpl implements Runnable

{

    public void run()

    {

        ……

    }

}

public class MainClass

{

    public static void main(String[] args)

    {

        Runnable target=new RunnableImpl();

        Thread thread=new Thread(target);

        thread.start();

    }

}

 

线程的状态

可以将线程分为创建、就绪、运行、休眠、挂起和死亡等类型。在不同类型的线程状态下,线程的特征如下所示。

— 创建状态:当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等运行资源。

— 就绪状态:对处于创建状态的线程调用Thread类的start()方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间片之外的其他系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。

— 运行状态:JVM的线程调度器选中处于就绪状态的线程,使其获得CPU时间片。

— 休眠状态:在线程运行过程中可以调用Thread类的sleep()方法,并在方法参数中指定线程的休眠时间将线程状态转换为休眠状态。这时,该线程在指定的休眠时间内,在不释放占用资源的情况下停止运行。时间到达后,线程重新进入运行状态。处于休眠状态的线程,可能遇上 java.lang.InterruptedException异常,从而被迫停止休眠。

— 挂起状态:可以通过调用Thread类的suspend()方法将线程的状态转换为挂起状态。这时,线程将释放占用的所有资源,由JVM调度转入临时存储空间,直至应用程序调用Thread类的resume()方法恢复线程运行。

—      死亡状态:当线程体运行结束或者调用线程对象的Thread类的stop()方法后线程将终止运行,由JVM收回线程占用的资源。

 

—  Thread.yield():当处于运行状态的线程调用Thread类的yield()方法之后,线程所拥有的CPU时间片被剥夺,重新回到就绪状态,等候JVM线程调度器的调度。

—  Thread.sleep():当处于运行状态的线程调用Thread类的sleep()方法之后,线程将在方法参数指定的时间段之内进入休眠状态,即在不释放任何运行资源的情况下停止运行。当休眠时间结束,或者捕获并响应由于Thread.interrupt()方法引起的 InterruptedException异常之后,线程回到运行状态。

—  Object.wait():当处于运行状态的线程调用Object类的wait()方法之后,线程将方法参数指定的时间段之内、在保留运行状态的情况下回到就绪状态。Object类的wait()方法支持无参调用,表示一直停止运行,等待被Object.notify()方法唤醒。

—  Object.notify():如果线程不处于就绪(等待)状态,则调用Object类的notify()方法不起任何作用;否则线程将重新获得竞争CPU时间片的资格,从等待之前的状态点开始运行。

—  Thread.suspend():调用Thread类的suspend()方法,将处于运行状态的线程置为挂起状态。该方法只能和Thread.resume ()配合使用,已被禁用。

—  Thread.resume():调用Thread类的resume()方法,将处于挂起状态的线程置为运行状态。该方法只能和Thread.suspend()配合使用,已被禁用。

—  Thread.stop():终止线程,线程将释放占用的全部资源。该方法已被禁用。

—      Thread.join():结束线程,并等待方法参数指定的时间段。在等待期间,如果线程已经结束,则立刻返回。Thread.join()方法是推荐的结束线程的方法。

—     Thread.suspend()、Thread.resume()、Thread.stop()方法已被禁用,因此线程的状态实际上只有5种:创建、就绪、运行、休眠和死亡。

 

线程的等待和唤醒

 使线程中断运行,返回就绪状态的手段是调用java.lang.Object对象的wait()方法;唤醒处于等待状态的线程的手段是调用java.lang.Object对象的notify()方法。对wait()和notify()方法说明如下。

—  void wait():使线程陷入一直等待的状态,除非被唤醒。

—  void wait(long timeout):使线程等待timeout指定的毫秒数。当等待时间已满,或者被唤醒,线程将重新被线程调度器调度。如果timeout为0,则等同于wait()。

—  void wait(long timeout,int nanos):使线程等待timeout指定的毫秒数+nanos指定的微秒数。当等待时间已满,或者被唤醒,线程将重新被线程调度器调度。如果timeout和nanos均为0,则等同于wait()。

—  void notify():唤醒在此对象上等待的一个线程,如果在此对象上有多个线程同时在等待,则任意唤醒其中一个。

—      void notifyAll():唤醒在此对象上等待的所有线程

 

线程的休眠和中断

线程的休眠状态和就绪(包括等待)状态的不同之处在于,处于休眠状态的线程并不释放运行资源,在休眠结束之后,不用等待被JVM线程调度器再度选中,而可以直接进入运行状态。结束休眠状态有两种途径:(1)休眠时间到达后,线程重新进入运行状态;(2)处于休眠状态的线程遇上java.lang.InterruptedException异常,从而被迫停止休眠。

使当前线程进入休眠状态的手段是调用Thread类的sleep()方法,该方法是静态方法,这意味着不用指定Thread对象便可以直接使用。打断某线程的休眠状态的手段是调用该线程对象的interrupt()方法。对 sleep()和interrupt()方法说明如下。

—  void sleep(long timeout):使当前线程休眠timeout指定的毫秒数。在休眠过程中,如果遇上中断,则抛出InterruptedException异常。

—  void sleep(long timeout,int nanos):使当前线程休眠timeout指定的毫秒数+nanos指定的微秒数。在休眠过程中,如果遇上中断,则抛出InterruptedException异常。

—      void interrupt():中断线程的休眠状态。

 

线程的终止

对于终止运行中的线程,Thread类原本提供了一个停止线程的方法:stop(),但是实践证明该方法具有固有的不安全性,因此已被弃用。结合目前我们已经掌握的技能,已经能够完美地终止线程,那便是利用线程的休眠和中断机制,在子线程中有意地为调度线程(比如创建线程的主线程)安排中断机会。

利用线程的休眠和中断机制,可以不留遗患地完美结束线程,是终止线程的推荐做法。例程虽然很简单,但是对于更复杂的场景,也可以遵照这个模型来设计线程的终止机制。

在和终止线程有关的方法中,Thread类还提供了一系列join()方法来等待线程结束。请注意,join()方法并不能终止某线程,而是提供了一个阻塞当前线程、等待某线程终止的途径。对join()方法说明如下。

—  void join():一直阻塞当前线程,等待线程结束。在等待过程中,如果遇上中断请求,则抛出InterruptedException异常。

—  void join(long timeout):在timeout指定的毫秒时间内阻塞当前线程,等待线程结束。在等待过程中,如果遇上中断请求,则抛出InterruptedException异常。

—  void join(long timeout, int nanos):在timeout指定的毫秒+nanos指定的微秒时间内阻塞当前线程,等待线程结束。在等待过程中,如果遇上中断请求,则抛出InterruptedException异常。

下面是一个简单的join()方法测试函数 ,读者可以自己运行体验下加上和不加join的区别:

public class testmain extends Thread  
{  
    public static int n = 0;  
 
    static synchronized void inc()  
    {  
        n++;  
        System.out.println("inc() is execed!");
    }  
    public void run()  
    {  
        for (int i = 0; i < 10; i++)  
            try 
            {  
                inc();  
                sleep(30);  // 为了使运行结果更随机,延迟3毫秒   
            }  
            catch (Exception e)  
            {  
            }                                        
    }  
    public static void main(String[] args) throws Exception  
    {  
     
        Thread mythread=new testmain();
        mythread.start();
        mythread.join();
        System.out.println("hello!");
        /*for (int i = 0; i < threads.length; i++)   // 100个线程都执行完后继续  
            threads[i].join(); */ 
        System.out.println("n=" + testmain.n);  
    }  
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值