Java多线程的目的是最大限度的利用CPU资源,实际上,操作系统的多进程实现了多任务的并发执行,程序的多线程实现了进程的并发执行。其前提是操作系统对多任务、多进程、多线程的支持。另外,在JVM的线程调度部分采用抢占式调度机制。
下面是一个简单的多线程的例子
public class SimpleThreadTest { public static void main(String[] args){ System.out.println(Thread.currentThread().getName()); new HelloThread().start(); Thread letter = new Thread(new EnglishWord()); Thread word = new Thread(new ChineseWord()); letter.start(); word.start(); }}class HelloThread extends Thread { public void run(){ System.out.println("Hello world! "+this.getName()+" is running "); }}class Letter { public void delay(){ long n = (long)(Math.random()*100000); for(int i =0; ijava SimpleThreadTest main Hello world! Thread-0 is running Java 爪哇 SE 标准版本 D:\Users\xhj>java SimpleThreadTest main Hello world! Thread-0 is running Java 爪哇 标准版本 SE
从上例可以看出主方法main()是运行在名为main的线程中即主线程,然后我们一步一步介绍Java多线程:
1. 建立线程(该部分参考)
使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。在上例中可看到两种情况的实现。线程总体分两类:用户线程和守候线程。守候线程专为后台服务,此类线程无需显示关闭,当所有线程运行结束,守候线程自动关闭,有线程在执行,守候线程就不会关闭。在线程的Thread对象上调用start()方法,而不是run()或者别的方法。在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。
在调用start()方法之后发生了一系列复杂的事情:
启动新的执行线程(具有新的调用栈);
该线程从新状态转移到可运行状态;
当该线程获得机会执行时,其目标run()方法将运行。
注意:
1、线程的名字,一个运行中的线程总是有名字的,名字有两个来源,一个是虚拟机自己给的名字,一个是你自己的定的名字。在没有指定线程名字的情况下,虚拟机总会为线程指定名字,并且主线程的名字总是main,非主线程的名字不确定;
2、线程都可以设置名字,也可以获取线程的名字,连主线程也不例外;
3、对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程;
4、在上面的代码中,只能保证:每个线程都将启动,每个线程都将运行直到完成。一系列线程以某种顺序启动并不意味着将按该顺序执行。对于任何一组启动的线程来说,调度程序不能保证其执行次序,持续时间也无法保证。
5、当线程目标run()方法结束时该线程完成。
6、一旦线程启动,它就永远不能再重新启动。只有一个新的线程可以被启动,并且只能一次。一个可运行的线程或死线程可以被重新启动。
7、线程的调度是JVM的一部分,在一个CPU的机器上上,实际上一次只能运行一个线程。一次只有一个线程栈执行。JVM线程调度程序决定实际运行哪个处于可运行状态的线程。众多可运行线程中的某一个会被选中做为当前线程。可运行线程被选择运行的顺序是没有保障的。
8、尽管通常采用队列形式,但这是没有保障的。队列形式是指当一个线程完成“一轮”时,它移到可运行队列的尾部等待,直到它最终排队到该队列的前端为止,它才能被再次选中。事实上,我们把它称为可运行池而不是一个可运行队列,目的是帮助认识线程并不都是以某种有保障的顺序排列成一个队列的事实。
2. 线程控制
2.1 setPriority()
不同的操作系统具有不同的优先处理机制,来确定那一个线程将被执行。例,在Unix和Linux操作系统中,线程的执行完全基于优先权队列。当JVM线程调度器讲一个标有优先权的线程送至操作系统执行时,操作系统的线程调度将根据这个优先级,把它置于相应的优先队列,等待执行,排在这个队列最前的线程将首先被执行。这样处理的缺点是,优先权低的线程有可能从未轮到执行。在Windows操作系统中,则使用优先权加时间段的处理机制。即使是优先权高的线程,它也不能垄断执行权,档期执行期段超时,也必须让步给低优先权的线程,这样使所有线程都有执行的机会。线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。
Java线程默认的优先权是5,Thread的setPriority方法可用来设置优先权,可以选择从1(最低)到10(最高)。Thread类中有三个常量,定义线程优先级范围:static int MAX_PRIORITY 线程可以具有的最高优先级;static int MIN_PRIORITY 线程可以具有的最低优先级;static int NORM_PRIORITY 分配给线程的默认优先级。
2.2 yield()
yield方法使当前运行的线程暂时停止,线程调度器将安排执行优先权高、排队在前的线程得以先运行。这个方法经常用在线程间相互替换运行的情况。
2.3 sleep()
sleep()方法可以是当前运行线程在指定毫秒时间处于暂停运行状态。当这个时间超时后,线程调度器将其设置为可执行状态。sleep()方法抛出InterruptedException,属检查性异常,必须提供异常处理机制才可运行。2.4 join()
join()方法实际上使调用它的线程插入运行,阻塞当前线程的执行,直到调用它的线程执行完毕,才恢复被阻塞线程的运行。如果调用它的线程是无限循环,其他线程则得不到执行。但如果其他线程中断它的运行,则会抛出异常InterruptedException,而停止继续运行。public class JoinExample { public static void main(String[] args){ Thread demoPI = new Thread(new Estimate()); demoPI.start(); try { demoPI.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("PI = "+Estimate.PI); }}class Estimate implements Runnable{ public static double PI = 0.0; private int sign = 1; @Override public void run() { // TODO Auto-generated method stub for (long i = 1; i <= 9999999; i += 2) { PI += 4.0*(double)sign/i; sign = -sign; } }} 运行结果: PI = 3.1415924535897797 若取消 demo.join();则运行结果为: PI = 0.0
2.5 interrupt()
interrupt()用来中断当前正在运行的线程。当一个线程被中断后,将抛出InterruptedException,其isInterrupted()的中断状态也将设置为真。注意,interrupt()并不中断线程的运行,但抛出中断异常并设置中断状态为真,可通过isInterrupted()检查中断状态。import java.util.Scanner;public class InterruptExample { public static void main(String[] args){ Thread service = new Service(); service.setName("Enthusiasm"); service.start(); Scanner sc = new Scanner(System.in); String command = ""; while (!command.equals("interrupt")) { command = sc.next(); } service.interrupt(); }}class Service extends Thread { @Override public void run() { // TODO Auto-generated method stub while (!this.isInterrupted()) { System.out.println(this.getName()+" is providing service ..."); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block //e.printStackTrace(); break; } } System.out.println("Service is interrupted ..."); } }
运行结果:
Enthusiasm is providing service ... Enthusiasm is providing service ... inEnthusiasm is providing service ... terruptEnthusiasm is providing service ...Service is interrupted ...
下面是一个小例子,利用多线程从一个二维数组中找出最大数
public class FindMaxExample { private static final int ROW = 100, COL = 200; public static long startTime, endTime; public static void main(String[] args) { MaxThread[] eachMaxThreads = new MaxThread[ROW]; double[][] matrix = Matrix.generator(ROW, COL); double max = Double.MIN_VALUE; for (int i = 0; i < eachMaxThreads.length; i++) { eachMaxThreads[i] = new MaxThread(matrix[i]); eachMaxThreads[i].start(); } startTime = System.currentTimeMillis(); System.out.println("start time: "+startTime); try { for (int i = 0; i < eachMaxThreads.length; i++) { eachMaxThreads[i].join(); max = Math.max(max, eachMaxThreads[i].getMax()); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } endTime = System.currentTimeMillis(); System.out.println("end time: "+endTime); System.out.println("Max of the matrix is: "+max); System.out.println("Completion time: "+(endTime-startTime)+" ms."); }}class MaxThread extends Thread { private double max = Double.MIN_VALUE; private double[] eachArray; public MaxThread(double[] eachArray) { this.eachArray = eachArray; } public void run() { for (int i = 0; i < eachArray.length; i++) { max = Math.max(max, eachArray[i]); } } public double getMax() { return max; }}class Matrix { public static double[][] generator(int row, int col) { double[][] matrix = new double[row][col]; for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { matrix[i][j] = Math.random() * 101; } } return matrix; }}
运行结果:
start time: 1365560078085end time: 1365560078085Max of the matrix is: 100.99740370977023Completion time: 0 ms.
大家可以试一下,用单线程来完成同样工作所需的时间。