Java的多线程机制
一、进程与线程的区别:
进程是指正在操作系统中运行的程序或软件。一个进程就代表一个已经启动并正在运行的程序。
线程:一个程序的执行路径,若此路径只有一条,则是单线程,若像迅雷这种软件(或程序),一次可以有5个执行路径,则称为多线程。
Windows操作系统是[多进程] _ 【多线程】 的系统。
1. 线程的概念:线程是一个系统中不同的执行路径。
package threaddemo;
/** * 在这个程序中,main方法调用了m1方法,m1方法又调用了m2,m3方法。 * @author Administrator * */ public class Demo1 {
public static void main(String[] args) { m1(); }
private static void m1() { m2(); m3();
}
private static void m3() { }
private static void m2() { } }
|
下面是此程序的执行路径 。是一条线,是一条执行路径。
程序在执行过程中每有一个分支,就会产生一个线程。
Main方法是一个程序执行的一个主分支,所以也称为主线程。
二、线程和进程。
进程是一个静态的概念。机器上的一个.class文件,一个.exe的可执行文件,这是一个进程。
程序的执行过程是第一步先把代码放入内存。放进去之后并没有马上执行。此时说明一个进程已经产生,进程已经准备开始了。但是还没有开始,这叫一个进程 。进程本身不能动。我们平时所说的进程的执行是指进程中主线程开始执行了。即main方法开始执行了。
在我们的机器当中运行的都是线程。
现在我们的windows操作系统可以同时运行多个可执行程序,即可以同时有多个进程启动(可以查看任务管理器),但需要明白的一点是这些进程只是静态概念,机器中真正在执行任务的是每一个进程中的多个线程。所以windows可以理解为是多进程多线程的操作系统。
当然Linux,unix也是,只有DOS不是。
三、分析CPU执行程序的原理。
CPU把自己的时间分成了一个个的时间片。每个时间片分配给不同程序的不同线程。然后轮流执行。因为速度快的原因,使人感觉像是在同时运行。但真正在一个时间点上只有一个程序的一个线程在运行。(只有双CPU或双核是真正的同时运行)
四、如何开始一个新的执行路径(即如何开始一个线程)
Java中的线程是通过 java.lang.Thread这个类来实现的。每一个Thread的对象代表一个线程。
创建线程和启动线程需要二步来执行。
第一步创建线程:有两种创建方式:
1.创建一个类继承Thread类,并重写run()方法,在此方法中写上你的程序要执行的任务。实例化这个自定义的类的对象,然后调用start()方法即可。
2. 创建一个实现了 Runnable接口的类,实例化这个自定义的类的对象,然后把这个对象作为new一个Thread类的对象时所需传递的参数传递给thread类的构造方法。
3. 每一个线程对象都是通过run()方法完成其功能。
4. 但线程的启动并不是直接调用run()方法,而是通过调用线程对象的start方法而间接调用run() 方法。注意:启动一个线程不是简单的一个run方法调用。
注意:因为Thread类和runnable接口中都有run()方法,run()方法是唯 一可以定义我们自己业务任务的地方。所以一定要重写与实现run()方法。
观察Thread类的下列构造方法:
Thread
(Runnable target)
分配新的 Thread
对象。
这里所传参数是Runnable这个接口的类型,我们这里需要传的是Runnable接口的实现类对象,即父类引用指向子类对象。这里也是多态的体现。既然是子类的对象,当然调用的就是子类重写的run方法。
观察代码:
public class TestThread1 { public static void main(String args[]) { Runner1 r = new Runner1(); r.start(); //r.run(); //Thread t = new Thread(r); //t.start();
for(int i=0; i<100; i++) { System.out.println("Main Thread:------" + i); } } }
//class Runner1 implements Runnable { class Runner1 extends Thread { public void run() { for(int i=0; i<100; i++) { System.out.println("Runner1 :" + i); } } } 分析:当main方法执行到start()方法时,一个程序的分支,即一个新的线程产生了。
而方法调用是下列执行流程:
|
但要注意:start()方法只是表明子线程已经准备好了,等待CPU分配时间片来调用。另一方面,CPU分给每个线程的时间片的时间长短也并不均匀,所以每一个线程的运行时间长短也不相同。但总是运行规律是两个线程或多个线程是交替运行。
五.面对两种创建线程的方式,该选择哪种方式?
应该选择实现接口,因为java只允许单继承,你从Thread类中继承了,就不可以再从其他类继承了。
六、线程的状态及状态转换。
本身一个线程对象被new出来以后,就创建好了。但不能运行 。
接下来调用start方法,但此方法被调用后也并不会马上执行,这只表明此线程已经准备就绪了,进入就绪状态。因为CPU此时可能正在运行其他线程,所以并不会马上切换过来来运行新线程。当CPU切换过来调度新线程时,则新线程进入运行状态。但这也并不表明新线程可以一直运行到任务全部结束为止。当新线程运行完CPU分配给他的时间片后,就会再次回到就绪状态等待。这是一个不断往复的过程,至到任务全部运行结束则进入终止状态。但在此过程中若发生了一些特殊情况,导致线程任务的运行受到阻塞,那就进入阻塞状态,直到阻塞状态解除后,重新进入就绪状态,再次等待CPU调度运行。这样往复直至任务全部结束。
七:如何控制线程状态的转换:
1. isAlive():除了终止状态和线程在调用start()方法之前,其他状态下都可以理解为线程“活”着。
package threaddemo;
/** * 在这个程序中,main方法调用了m1方法,m1方法又调用了m2,m3方法。 * @author Administrator * */ public class Demo1 {
public static void main(String[] args) { A a=new A(); Thread aThread=new Thread(a); aThread.setName("A线程"); aThread.start(); System.out.println(aThread.isAlive()); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(aThread.isAlive()); }
}
class A implements Runnable{
public void run() { for(int i=0;i<20;i++){ System.out.println(i); }
}
}
|
2. setPriority():线程是有优先级的。所谓优先级是说:优先级越高的线程所获得的CPU的运行时间越多。但优先级低的线程也是有执行机会的。但这种机会相比会显示“来之不易”。
3. sleep():当前线程睡眠的毫秒数。
4. join():合并线程。在主线程中调用子线程对象的join()方法,就是主线程要等待子线程执行完毕才能继续执行(相当于两个线程合并成一个),这会导致子线程顺序先于主线程顺序。
5. yield():让出CPU,给其他线程执行的机会(只是让出一下,但只要一调用此方法,就会马上切换成另一个线程)
6. wait,notify,notifyAll,:线程同步中会涉及。
八、线程调度方法的详解:
1.Sleep():是Thread类的静态方法。
static void | sleep(long millis) |
Sleep(可以通过此方法调整程序执行的快慢。例如每隔1000毫秒显示一次当前时间)
public static void sleep(long millis)
throws InterruptedException
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。该线程不丢失任何监视器的所属权。
参数:
millis - 以毫秒为单位的休眠时间。
抛出:
InterruptedException - 如果另一个线程中断了当前线程。当抛出该异常时,当前线程的中断状态 被清除。
当睡眠之中的线程被别的线程打断了就会抛出此异常。
import java.util.*; public class TestInterrupt { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); try {Thread.sleep(10000);}//主线程睡眠了10秒钟,这样就使子线程有了10秒钟的运行时间,子线程运行了10秒,把当前的时间打印了出来。 catch (InterruptedException e) {} thread.interrupt();//10秒后,主线程调用了子线程的打扰方法,给子线程浇了一盆凉水。使子线程发生InterruptException,子线程执行catch块中的return方法结束程序,因此这句代码就可以让子线程结束。这是一种让线程停止的方式,但并不是最好的方式。这里注意更不应该使用stop方法,因为此方法会使线程立刻停止,而不会给线程一些收尾释放资源的机会。 } }
class MyThread extends Thread { boolean flag = true; public void run(){//子线程任务区域 while(flag){ System.out.println("==="+new Date()+"==="); try { sleep(1000);//子线程睡眠了。 } catch (InterruptedException e) { return;// 表示结束 //flag = false;//比较好的处理方式 } } } }
|