一个进程在运行时至少会有一个线程在运行,在这种情况下Java中也是存在的,这些线程在后台默默低执行,例如,调用public static void main()方法的线程就是这样的,而且它是由JVM创建。
package test;
public class test {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
}
}
在控制台输出的main其实就是一个名称为main的线程在执行main()方法中的代码。另外,需要说明一下,在控制台中输出的main和main方法没有任何关系,她们仅仅是名字相同而已。
1.1继承Thread类
Java的JDK开发包已经自带了多线程技术的支持,通过它可以方便地进行多线程编程。实现多线程编程主要有两种方式:一种是继承Thread类,另外一种是实现Runnable接口。
在学习如何创建新的线程前,先来看看Thread类的声明结构:
public class Thread implements Runnable
从上面的源代码中可以发现,Thread类实现了Runnable接口,它们之间具有多态性,多态结构的实例代码如下:
Runnable run1 = new Thread();
Runnable run2 = new MyThread();
Thread t1 = new MyThread();
其实使用继承Thread类的方式创建新线程是,最大的局限是不支持多继承,因为java语言的特点是单继承,所以为了支持多继承,完全可以实现Runnable接口,即一边实现一边继承,但是这两种方式创建线程的功能是一样的,没有本质的区别。
本节主要介绍第一种方式。如下代码:
package com.mythread.www;
public class MyThread extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
System.out.println("MyThread");
}
}
运行类代码如下:
package test;
import com.mythread.www.MyThread;;
public class Run {
public static void main(String[] args) {
final MyThread myThread = new MyThread();
myThread.start();
System.out.println("运行结束!");// 耗时小
}
}
上面代码使用start()方法来启动一个线程,线程启动后会自动调用线程对象中的run()方法,run()方法里面的代码就是线程对象要执行的任务,是线程执行任务的入口。
程序运行结果如下图所示
从上图的程序运行结果来看,MyThread.java类中的run()方法的执行时间相对于输出“运行结束!”的执行时间晚一些,因为start()方法的执行比较耗时间,这也增加了先输出“运行结束!”字符串的概率。start()方法耗时的原因是执行了多个步骤,步骤如下:
(1)通过JVM告诉操作系统创建Thread。
(2)操作系统开辟内存并使用Windows SDK中的create Thread()函数创建Thread线程对象。
(3)操作系统对Thread对象进行调度,以确定执行时机。
(4)Thread在操作系统中被成功执行。
以上4步完整地执行后所消耗的时间一定大于输出“运行结束”字符串的时间,另外,main方法线程执行start()方法时候不必等待4步都执行完毕,而是立即继续执行start()方法后面的代码,这四步与输出“运行结束”的代码一同执行,但是由于“运行结束”耗时比较少,所以在大多数的情况下,先输出“运行结束”,后输出“My Thread”
输出上面的结果说明执行完整的start()方法的4步后,才执行输出“运行结束!”字符串的代码,这也说明线程执行的顺序具有随机性。然而由于输出这种结果的机会很小,使用手动的方式来重复执行“Run as—>java Application难以重现,这时候可以认为地制造这种输出结果,即在执行输出”运行结束!“代码之前先执行代码”Thread.sleep(200),让run()方法有充足的时间来先输出“MyThread”。后输出“运行结束!”实例代码如下:
package test;
import com.mythread.www.MyThread;;
public class Run2 {
public static void main(String[] args) throws InterruptedException {
final MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(200);
System.out.println("运行结束!");// 耗时小
}
}
在使用线程技术时,代码的运行结果与代码的执行顺序或者调用顺序是无关的。另外,线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run()方法,所以先输出“运行结束!”和先输出“MyThread”具有不确定性。
注意:
如果多次调用start()方法,则会出现Exception in Thread“main”java.lang.lllegal - ThreadStateException。
1.2.2使用常见命令分析线程的信息
可以在运行的进程中创建线程,如果想要查看这些线程的状态和信息,则可以采用3种常见命令,它们分别是jps+jstack.exe、jmc.exe、jvisualvm.exe,它们在jdk\bin文件夹中
创建测试使用的程序并运行,代码如下:
package test.run;
public class Run3 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread() {
public void run() {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
}
1.2.3线程随机性的展现
前面介绍过线程的调用时随机的,但是这一点并没有在代码中得以体现,都是理论的内容,所以本节将演示线程的随机性
package MyThread.java;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("run="+Thread.currentThread().getName());
}
}
}
再创建运行类Test.java
package test;
import MyThread.java.*;;
public class Test {
public static void main(String[] args) {
final MyThread thread = new MyThread();
thread.setName("mythread");
thread.start();
for (int i = 0; i < 10000; i++) {
System.out.println("main=" + Thread.currentThread().getName());
}
}
}
Thread.java类中的start()方法通知“线程规划器”----次线程已经准备就绪,准备调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,即让线程执行具体的任务,具有随机顺序执行的结果。
如果调用代码“Thread.run();而不是”thread.start();其实就不是异步执行了,那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run()方法,也就是等于run()方法中的代码完毕后在执行后面的代码。
以异步方式运行的效果如下图所示。
多线程随机输出的原因是CPU将时间片分给不同线程,线程获得时间片后就执行任务,所以这些线程在交替低执行并输出,导致输出结果呈现乱序的效果。时间片段即CPU分配给每个程序的时间。每个线程被分配一个时间片,在当前的时间片内CPU去执行线程中的任务。需要注意的是,CPU在不同的线程上进行切换是需要耗时的,所以并不是创建的线程越多,软件的运行效率就越高,相反,线程数过多反而会降低软件的执行效率。