一个进程正在运行时至少会有1个线程在运行,这种情况在Java中也是存在的。这些线程在后台默默地执行,比如调用public static void main(String[] args)方法的线程就是这样的,而且它是由JVM创建的。
在Java的JDK开发包中,已经自带了对多线程的支持,可以很方便地进行多线程编程。实现多线程编程的方式主要有两种,一种是继承Thread类,另一种是实现Runnable接口,Thread类的结构如下:
从上面的源代码中可以发现,Thread类实现了Runnable接口,他们之间具有多态关系。
其实,使用继承Thread类的方式创建新线程时,最大的局限就是不支持多继承,因为Java语言的特点就是单根继承,所以为了支持多继承,完全可以实现Runnable接口的方式,一边实现一边继承。但用这两种方式创建的线程在工作时的性质是一样的,没有本质的区别。
创建一个自定义的线程类MyThread.java,此类继承自Thread,并且重写run方法。
1 package com.mythread.www; 2 3 public class MyThread extends Thread { 4 5 @Override 6 public void run() { 7 super.run(); 8 System.out.println("MyThread"); 9 }10 11 }
运行类代码如下:
1 package test; 2 3 import com.mythread.www.MyThread; 4 5 public class Run { 6 7 public static void main(String[] args) { 8 MyThread myThread = new MyThread(); 9 myThread.start();10 System.out.println("运行结束!");11 }12 13 }
运行结果如下:
这行代码执行后,线程已处于就绪状态,但CPU还未分配时间片段给此线程,而是继续执行主线程main。
从运行结果来看,MyThread.java类中的run方法执行的时间比较晚,这也说明在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。
线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法,所以就会出现先打印 “运行结束!” 后输出 “MyThread” 这样的结果了。
如果多次调用start()方法,则会出现如下异常:
1 package test; 2 3 import com.mythread.www.MyThread; 4 5 public class Run { 6 7 public static void main(String[] args) { 8 MyThread myThread = new MyThread(); 9 myThread.start();10 myThread.start();11 System.out.println("运行结束!");12 }13 14 }
看下Thread类的源码就清楚了
当第一次调用start()方法时,threadStatus初始值为0,表示线程还未开始执行,执行完start0()方法后,threadStatus的值将改变,不再为0,表示线程已经处于执行状态,当再次调用start()方法时,就会抛出上述异常。
--------------------------------------------------------------------------------------------------------------------------------
1 package test; 2 3 public class MyThread extends Thread { 4 5 @Override 6 public void run() { 7 for (int i = 0; i < 5; i++) { 8 int time = (int)(Math.random() * 1000); 9 // System.out.println("myThreadTime:" + time);10 try {11 Thread.sleep(time);12 } catch (InterruptedException e) {13 // TODO Auto-generated catch block14 e.printStackTrace();15 }16 System.out.println("myThread=" + Thread.currentThread().getName());17 }18 }19 20 }
1 package test; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 MyThread myThread = new MyThread(); 7 myThread.setName("myThread"); 8 myThread.start(); 9 // myThread.run();10 for (int i = 0; i < 5; i++) {11 int time = (int)(Math.random() * 1000);12 // System.out.println("mainTime:" + time);13 try {14 Thread.sleep(time);15 } catch (InterruptedException e) {16 // TODO Auto-generated catch block17 e.printStackTrace();18 }19 System.out.println("main=" + Thread.currentThread().getName());20 }21 }22 23 }
上述代码执行后的结果如下:
把myThread.run()这段代码的注释去掉,再把myThread.start()注释掉后,运行的结果如下:
Thread.java类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,也就是使线程得到运行,启动线程,具有异步执行的效果。如果调用代码myThread.run()就不是异步执行了,而是同步,那么此线程对象并不给“线程规划器”来进行处理,而是由main主线程来调用run()方法,也就是必须等run()方法中的代码执行完后才可以执行后面的代码。
另外需要注意的是,执行start()方法的顺序不代表线程启动的顺序。
1 package test; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 MyThread t0 = new MyThread(0); 7 MyThread t1 = new MyThread(1); 8 MyThread t2 = new MyThread(2); 9 MyThread t3 = new MyThread(3);10 MyThread t4 = new MyThread(4);11 MyThread t5 = new MyThread(5);12 MyThread t6 = new MyThread(6);13 MyThread t7 = new MyThread(7);14 MyThread t8 = new MyThread(8);15 MyThread t9 = new MyThread(9);16 t0.start();17 t1.start();18 t2.start();19 t3.start();20 t4.start();21 t5.start();22 t6.start();23 t7.start();24 t8.start();25 t9.start(); 26 }27 28 }
执行上述代码后,运行结果如下:
--------------------------------------------------------------------------------------------------------------------------------
如果欲创建的线程类已经有一个父类了,这时就不能再继承自Thread类了,因为Java不支持多继承,所以就需要实现Runnable接口来应对这种情况。
1 package test; 2 3 public class MyRunnable implements Runnable { 4 5 @Override 6 public void run() { 7 System.out.println("运行中!"); 8 } 9 10 }
如何使用这个MyRunnable.java类呢?这就要看下Thread.java的构造函数了,如下图所示,其中三角符号标识的是default构造函数,其它的均为public构造函数
1 package test; 2 3 public class Run { 4 5 public static void main(String[] args) { 6 MyRunnable myRunnable = new MyRunnable(); 7 Thread thread = new Thread(myRunnable); 8 thread.start(); 9 System.out.println("运行结束!");10 }11 12 }
运行上述代码后,结果如下:
另外需要说明的是,Thread.java类也实现了Runnable接口,那就意味着构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run()方法交由其他的线程进行调用。
--------------------------------------------------------------------------------------------------------------------------------
实例变量与线程安全
1、不共享数据的情况
1 package thread; 2 3 public class MyThread extends Thread { 4 5 private int count = 5; 6 7 public MyThread(String name) { 8 super(); 9 this.setName(name);10 }11 12 @Override13 public void run() {14 while (count > 0) {15 count--;16 System.out.println(this.currentThread().getName() +17 ":count = " + count);18 } 19 }20 21 public static void main(String[] args) {22 MyThread a = new MyThread("A");23 MyThread b = new MyThread("B");24 MyThread c = new MyThread("C");25 a.start();26 b.start();27 c.start();28 }29 30 }
执行上述代码运行结果如下:
从运行结果可以看出,每个线程都有各自的count变量,自己减少自己的count变量的值。这样的情况就是变量不共享,此示例并不存在多个线程访问同一个实例变量的情况。
2、共享数据的情况