也许我们经常做的事就是一边聊QQ一边听音乐,有的还一边玩游戏,这一切都是多任务实现的。而java语言使用多线程实现了一个程序中的多个任务同时运行。
那到底是不是同时执行多线程呢?显然不是,CPU一次只能然一个线程执行,由于线程之间切换速度很快,所以在我们看来是同时执行的。
java中如何实现多线程:
1.继承Thread类创建多线程。
2.实现Runnable接口创建多线程。
继承Thread类,重写run()方法是实现多线程的方式之一,Thread类本身就实现了Runnable接口。
如下例继承Thread类:
public class ThreadTest extends Thread{
public void run(){
while(true){
String name = Thread.currentThread().getName();
sayHello(name);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void sayHello(String name){
System.out.println(name+"说hello");
}
public static void main(String [] agrs){
ThreadTest tt1 = new ThreadTest();
ThreadTest tt2 = new ThreadTest();
tt1.start();
tt2.start();
}
}
我的运行结果(每个人的运行结果都会不同):
Thread-0说hello
Thread-1说hello
Thread-1说hello
Thread-0说hello
Thread-1说hello
Thread-0说hello
Thread-1说hello
Thread-0说hello
可以看出线程是一个一个执行的,并且不是按顺序执行的,红色标记的地方说明。cpu为每个线程分配了时间片,哪个线程得到了CPU,哪个线程就得到执行,这个在后面再说明。
实现Runnable接口,实现run()方法来创建多线程,如下例:
public class ThreadTest implements Runnable{
public void run() {
while(true){
String name = Thread.currentThread().getName();
sayHello(name);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void sayHello(String name){
System.out.println(name+"说hello");
}
public static void main(String [] agrs){
Runnable r = new ThreadTest();
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
thread1.start();
thread2.start();
}
}
这里只是创建线程对象有稍微的区别。
那么他们之间有什么区别?甚至有的人问哪个更好?因为我就这么问过,但是在网上找到的答案基本都很片面,只找到了一个说的比较好,还需要时间研究研究。我列出来大家看看:
1.适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想。
2.可以避免由于java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
3.有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程可以操作相同的数据,与它们的代码无关。当共享访问相同的对象时,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例
那么实现多线程都是对run()方法重写,为什么不是直接调用run()方法而是调用start()方法呢?这里我们来看看run()和start()方法的区别。
start():
通过调用start()方法来启动线程,此时线程处于就绪状态,一旦得到CPU时间片就执行run()方法,方法run()称为线程体,它包含了要执行这个线程的内容,当run()方法运行结束,线程随即终止。
run():
run()方法只是Thread类的一个普通方法,如果直接调用run()方法,程序中只有主线程这一个线程,执行路径只有一条,要这个方法执行完才能执行下面的代码,这就没达到多线程的目的。
继续看如上例子,如果是:
thread1.run();
thread2.run();
那么运行结果为:
main说hello
main说hello
main说hello
main说hello
main说hello
main说hello
main说hello
main就是我们一直以来接触的程序的入口(主方法),其实它本身就是一个线程,并且是主线程。
线程的生命周期:
线程具有生命周期,包含3个状态,使用线程实例调用start()方法之前,线程处于出生状态,当调用start()方法后,线程就处于就绪状态,当线程得到系统资源后进入运行状态,当时间片结束线程又回到就绪状态,继续等待资源。
而在线程执行过程中,线程又可能转入等待,睡眠,阻塞或者死亡状态。
1.线程在执行过程中调用wait()方法进入等待状态,这时线程释放持有的资源,必须采用notify()或者notifyAll()方法来唤醒该进程或者被别的对象调用interrupt()方法打断,这个方法会抛出一个InterruptedException异常,这个异常如果不捕获处理,线程就会异常终止,当线程被唤醒后,再次进入就绪状态,等待资源。
2.线程在执行过程中调用sleep()方法,进入睡眠状态,这个过程线程不会释放持有的资源,待睡眠时间完后即进入就绪状态,不会立即进入执行状态,因为线程调度机制恢复线程的运行也需要时间。当然睡眠状态也会被interrupt(0方法打断,效果和wait()一样。
3.线程执行过程中,因为某些原因而使其停止执行。当线程阻塞后,别的贤臣就有机会执行,当线程阻塞被解除后进入就绪状态,等待被调度。
阻塞原因有:
a.线程调用sleep()方法放弃CPU时间片。
b.线程调用wait()方法,等待别的线程来唤醒它。
c.线程调用阻塞IO方法,方法返回前处于阻塞状态。(IO后面会分析)
d.线程执行需要资源,在执行过程中,某些资源被别的线程持有,则该线程会处于阻塞状态来等待资源的获取。
e.程序调用suspend()方法将该线程挂起,该方法容易产生死锁。
4.线程进入死亡状态也是有原因的。
死亡原因:
a.线程执行完run()方法进入死亡状态。
b.线程通过某种方式终止线程使其进入死亡状态(接下来分析)。
c.线程抛出未捕获的线程使线程异常终止。
终止线程:
在早期的JDK中提供了stop()方法来终止线程,但是新版本不建议使用,提倡用布尔标记控制循环的停止来达到终止线程的目的。
1.在循环体中,通过条件判断,符合条件的就break;跳出循环,终止线程。
2.循环条件不要写成true,而是手动控制循环条件的false来达到终止线程的目的。
线程的调度:
1.线程执行前,通过优先级来达到线程调度的目的,优先级高的线程优先得到CPU执行。线程的优先级可以在程序中表明该线程的重要级,学过操作系统的都明白有很多的调度算法,这里不细说了。
2.线程执行过程中,可以通过join()方法来调度线程,比如:
threadA = new Thread(new Runnable(){
public void run() {
for(int i = 0;i<=100;i++){
if(i >= 50){
threadB.join();
}
}
}
});
显然在线程A执行时,当i=50的时候,就会执行线程B,直到线程B执行完,线程A才会继续执行,这样也能达到线程调度的目的。
线程安全:
在多线程中,说到什么最重要,要属线程安全当之无愧,引入多线程的目的就是对大量任务进行有序的管理,通过多个任务混合使用,可以更有效的利用计算机的资源。
使用多线程,会发生两个以上的线程抢占资源的问题,必须防止资源访问的冲突。java提供线程同步机制可以防止资源访问的冲突(下篇分析)。
欲知后事如何,请看下集。。。