java多线程
在java中其实即使我们没有定义多线程,java仍然不是一个单线程,例如,一个简单的java程序,仅仅是打印输出一条语句,它的主线程是从main方法开始执行,一直执行完main中的所有语句,完成main()方法后消亡,也许你会说,这就是一个单线程,但是,在jvm中还有一个不为人所知的线程在运行,在初学java时,可能就有人告诉你java会自动进行垃圾搜集,那么垃圾搜集是如何进行的呢?其实,就是通过一个线程来进行的。垃圾回收线程清除被废弃的对象,并回收它们占用的内存。因此,即使是一个只完成打印“Hello, world”任务的Java程序也是运行在一个多线程的环境中,这两个线程便是主线程和垃圾回收线程。
在java.lang包中,有一个Thread类用来代表线程对象,Thread类也是进行多线程编程时主要面对的类。java.lang包中的类不需引入便可以在程序中直接使用
1)如何获取当前线程对象
以下的代码行先获取当前的线程对象,继而获取当前线程对象的标识:
System.out.println(Thread.currentThread().getId());
线程ID是一个正的长整型数,在创建该线程时生成。线程ID是唯一的,并在线程的生命周期内保持不变。线程被终止后,该线程ID可以被重新分配给其他的线程。
2)如何给线程命名
无论线程处于何种运行状态,均可以调用Thread类的setName()方法改变线程名称。主线程的名称也可以被改变。
以下的代码片段先打印主线程的名称,然后将其改变为“MyThread”:
System.out.println("线程的原名称是:"+Thread.currentThread().getName());
Thread.currentThread().setName("MyThread");
System.out.println("线程的新名称是:"+Thread.currentThread().getName());
运行效果如下:
线程的原名称是:main
线程的新名称是:MyThread
3)如何构造和启动线程
—
—
下面代码片段构造了两个线程,其中thread1由Java虚拟机根据线程命名规则指定,thread2命名为“MyThread”:
Thread thread1=new Thread();
Thread thread2=new Thread("MyThread");
以上构建的线程都是没有任何运行逻辑的,也就是线程体为空。线程的线程体包含在Thread类的run()方法中,可以在构建线程对象时重载run()方法,写入自定义的逻辑。构建线程之后启动线程的方法是利用Thread类的start()方法。对Thread类的run()和start()方法说明如下。
—
—
主线程一旦执行Thread对象的start()方法启动子线程,子线程的执行便和主线程形成了异步的关系,主线程不会等待子线程执行结束,而是立刻执行下面的语句。
4)如何设置线程优先级
5)守护线程
守护线程还有另一层含义:当创建守护线程的父线程终止时,作为子线程的守护线程也自动终止。反之,如果一个子线程不是守护线程,即使父线程终止了,它也不会终止。
当一个线程被创建时,它默认不是守护线程。
面的代码片段演示守护线程的用法。线程parent创建了一个子线程child,子线程child的循环体是一个无限循环,不断地打印“Child runs”字样。在child线程不是守护线程的情况下,即使parent线程终止了,child线程仍然在运行:
Thread parent=new Thread()
{
};
parent.start();
如果希望父线程终止时,子线程自动终止,只需要将“child.setDaemon(false);”改为“child.setDaemon(true);”即可。
6)
— 将实现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类的构造方法如下。
—
—
基于Runnable接口创建新线程的代码如下所示:
public class RunnableImpl implements Runnable
{
}
public class MainClass
{
}
线程的状态
可以将线程分为创建、就绪、运行、休眠、挂起和死亡等类型。在不同类型的线程状态下,线程的特征如下所示。
— 创建状态:当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等运行资源。
— 就绪状态:对处于创建状态的线程调用Thread类的start()方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间片之外的其他系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。
— 运行状态:JVM的线程调度器选中处于就绪状态的线程,使其获得CPU时间片。
— 休眠状态:在线程运行过程中可以调用Thread类的sleep()方法,并在方法参数中指定线程的休眠时间将线程状态转换为休眠状态。这时,该线程在指定的休眠时间内,在不释放占用资源的情况下停止运行。时间到达后,线程重新进入运行状态。处于休眠状态的线程,可能遇上java.lang.InterruptedException异常,从而被迫停止休眠。
— 挂起状态:可以通过调用Thread类的suspend()方法将线程的状态转换为挂起状态。这时,线程将释放占用的所有资源,由JVM调度转入临时存储空间,直至应用程序调用Thread类的resume()方法恢复线程运行。
—
—
—
—
—
—
—
—
—
—
线程的等待和唤醒
—
—
—
—
—
线程的休眠和中断
线程的休眠状态和就绪(包括等待)状态的不同之处在于,处于休眠状态的线程并不释放运行资源,在休眠结束之后,不用等待被JVM线程调度器再度选中,而可以直接进入运行状态。结束休眠状态有两种途径:(1)休眠时间到达后,线程重新进入运行状态;(2)处于休眠状态的线程遇上java.lang.InterruptedException异常,从而被迫停止休眠。
使当前线程进入休眠状态的手段是调用Thread类的sleep()方法,该方法是静态方法,这意味着不用指定Thread对象便可以直接使用。打断某线程的休眠状态的手段是调用该线程对象的interrupt()方法。对sleep()和interrupt()方法说明如下。
—
—
—
线程的终止
对于终止运行中的线程,Thread类原本提供了一个停止线程的方法:stop(),但是实践证明该方法具有固有的不安全性,因此已被弃用。结合目前我们已经掌握的技能,已经能够完美地终止线程,那便是利用线程的休眠和中断机制,在子线程中有意地为调度线程(比如创建线程的主线程)安排中断机会。
利用线程的休眠和中断机制,可以不留遗患地完美结束线程,是终止线程的推荐做法。例程虽然很简单,但是对于更复杂的场景,也可以遵照这个模型来设计线程的终止机制。
在和终止线程有关的方法中,Thread类还提供了一系列join()方法来等待线程结束。请注意,join()方法并不能终止某线程,而是提供了一个阻塞当前线程、等待某线程终止的途径。对join()方法说明如下。
—
—
—