一个进程正在运行时至少会有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 block 14 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 block 17 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 @Override 13 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、共享数据的情况