什么是线程? 不急,我们先来认识一个问题,我们以往写的代码大多数是单线程的,即一个程序只有一条从头到尾的执行线索。但是这样的机制是远远不能满足我们的需求的,就好像我们在看电视的时候就只能看电视,不能动胳膊动手的,多憋屈是不是?所以。。。就有了多线程啦!
什么是多线程?
Java的一大特点就是内置对多线程的支持。多线程就是指同时存在几个执行体,按几种不同所谓执行方法共同工作,可以同时处理多个任务。但是我们要注意的是:这里的“同时”不是真正物理意义上的同时,只是它们之间切换的时间很快,于是就给我们了一个错觉-----它们在同时执行。刚刚我们谈到了切换,这里的切换就是java快速的把控制从一个线程切换到另一个线程。
程序、进程与线程:
程序:程序只是一段静态的代码,当我们需要计算机做事时,必须告诉计算机我们的指令,那么这些指令就是程序。
进程:进程是程序的一次动态执行过程,它对应了从代码加载、执行及执行完毕的一个完整过程。
线程:线程是比进程更小的执行单位,就拿一个我们生活中的例子来说:当我们在参加800M长跑比赛时,800M长跑就相当于一个进程,如果让选手跑完一个再接着下一个选手,这就相当于单线程,但是如果让选手同时跑,这个就相当于多线程啦!由此可见,一个进程可以有多个线程,java虚拟机jvm会默认给一个主线程,这个线程是从main方法里开始。我们也可以自己创建线程,这将在后面提到具体的实现方法。
线程的生命周期(线程模型):
新建-->就绪-->(阻塞)-->运行-->死亡
新建:当线程使用new创建了一个对象后,该线程就处于新建状态
就绪:当对象调用了start()方法后,就处于就绪状态
阻塞:当线程调用了sleep(int millsecond)方法或者调用了一个阻塞式IO方法时,就处于阻塞状态
运行:如果处于就绪状态的线程获得jvm的指令后,执行run()方法
死亡:线程执行完run()方法或者调用stop()或者其他强制关闭线程方法后,线程会释放出占用的内存空间,此时线程就处于死亡状态
线程的创建:
线程有四种创建方式:
★继承Thread类
创建线程可以通过Thread类和它的子类创建,这里继承了Thread类
当然就是通过它的子类来创建线程对象啦!创建对象后我们还有重写Thread类的run()方法,因为Thread类中的run()方法是空的,如果我们不重写的话,那么这个线程就相当于什么都没做了,run()方法里写我们想要线程执行的操作语句。下面我们通过一个例子来具体认识一下这个方法:
Sleep(int millsecond)方法是Thread类中的静态方法,它的作用是让该线程暂停millsecond毫秒后再继续执行线程,这样可以大大降低CPU的使用率。
/** * Thread类的实现 * @author Administrator * */ public class ThreadDemo extends Thread { /** * 程序的入口 */ public static void main(String[] args) { for(int i=1;i<10;i++){ ThreadDemo td = new ThreadDemo(i); td.start();//启动线程 } System.out.println("main方法结束了!"); Thread.currentThread(); } private int id ; public ThreadDemo(int id){ this.id = id; } /** * 重写线程的运行方法 */ public void run(){ while(true){ test(); System.out.println("这是第"+id+"个线程!运行时间是:"+System.currentTimeMillis()); try { //让线程休眠一段时间 Thread.sleep(id*1000); } catch (InterruptedException e) { e.printStackTrace(); } } } private void test() { Thread.currentThread(); } }
★实现Rennable接口
使用继承Thread类来创建线程这个方法的优点是可以在子类中新定义一些我们需要的属性和方法,但是别忘了,在java中是不允许多继承的,所以当我们需要继承另一个类时就不好办啦!这时,我们可以通过实现Rennable接口来创建线程。也就是直接用Thread类来创建线程----Thread(Rennable target),参数是一个Rennable类型的接口。当我们继承了一个接口时首先要做的就是实现接口中的方法,所以我们首先要写Rennable接口中run()方法。然后再通过start();方法开始这个运行线程,当调用了start();方法后程序会自动调用我们重写的Rennable接口中run()方法。
★匿名内部类
以匿名内部类的方式创建线程即直接在方法内部创建线程,当方法被调用时,线程就启动了,当然,内部线程也需要通过start();启动。
例:
import java.io.File; public class RunnableDemo implements Runnable { private int count = 1; private boolean state = true; public void run() { while (state) { if (count == 100) { state = false; } System.out.println("这个线程执行了" + count + "次!"); count++; } } public static void main(String[] args) { RunnableDemo rd = new RunnableDemo(); // Thread t = new Thread(rd); // t.start(); rd.testRunnable("src/cn/netjava"); } public void testRunnable(String path) { File file = new File(path); // 获取到file下所有的目录和文件 final File[] array = file.listFiles(); // 判断array是否为空或者长度为0 if (null == array || array.length == 0) { return; } // 循环遍历文件 for (int i = 0; i < array.length; i++) { final File f = array[i]; // 使用匿名内部类的方式来实现线程 Runnable run = new Runnable() { /** * 运行方法 */ public void run() { int count = countFile(f); System.out.println(f.getAbsolutePath()+"下的文件有:"+count); } }; Thread t = new Thread(run); t.start(); } } /** * 文件统计方法 * * @param path路径 */ public int countFile(File f) { int count = 0; // 获取到file下所有的目录和文件 final File[] array = f.listFiles(); // 判断array是否为空或者长度为0 if (null == array || array.length == 0) { return 0; } // 循环遍历文件 for (int i = 0; i < array.length; i++) { File f1 = array[i]; if(f1.isFile()){ count++; }else{ count+=countFile(f1); } } return count; } }
注意:使用匿名内部类创建线程时,如果想要往run方法中传参数的话,这个参数必须是fianl型的。
★(定时任务)TimerTask
我们还可以通过继承TimerTask类来实现线程的定时执行,比如定时发送邮件、闹钟等。TimerTask实现了Runnable接口,待执行的任务置于run()中。Timer是一个安排TimerTask的类此此两者一般一起工作。所应用时首先硬创建一个TimerTask的对象,然后用一个Timer的类对象安排执行它,可以简单地额归纳为下面几步:
创建一个定时器对象:Timer time=new Timer();
创建一个定时任务对象:TimerPrint tp=new TimerPrint();
注:这里的TimerPrint 是一个我们自己写的一个实现了定时任务方法的类
调度这个定时任务在程序启动后m毫秒后,每隔n毫秒发生一次
time.schedile(tp,m,n);