Java支持多线程编程,因此用Java编写的应用程序可以同时执行多个任务。Java的多线程机制使用起来非常方便,用户只需关注程序细节的实现,而不用担心后台的多任务系统。本文java培训将为大家讲解有关Java多线程的基础知识,主要内容有多线程的概念、创建、优势和生命周期。
1、什么是多线程
在早期的计算机中时没有操作系统的,计算机开启后只能执行一个程序,直到结束。操作系统的出现使得计算机可以同时执行多个程序,操作系统为每个程序分配不同的进程,每个进程拥有独立的句柄、资源等,使得计算机可以同时执行多个程序。但是进程的创建和销毁耗费的代价太大,因此衍生出线程的概念。允许在一个进程中创建多个线程,这些线程共享进程的资源,并且每个线程拥有自己独立的程序计数器、线程局部变量等资源。线程也被称为进程的轻量型运动实体。
2、创建多线程和启动
(1)继承Thread类创建线程类
通过继承Thread类创建线程类的具体步骤和具体代码如下:
定义一个继承Thread类的子类,并重写该类的run()方法;
创建Thread子类的实例,即创建了线程对象;
调用该线程对象的start()方法启动线程。
class SomeThead extends Thraad{
public void run(){
//do something here
}
}
public static void main(String[]args){
SomeThread oneThread=new SomeThread();
步骤3:启动多线程:
oneThread.start();
}
(2)实现Runnable接口创建线程类
通过实现Runnable接口创建线程类的具体步骤和具体代码如下:
定义Runnable接口的实现类,并重写该接口的run()方法;
创建Runnable实现类的实例,并以此实例作为Thread的target对象,即该Thread对象才是真正的线程对象。
class SomeRunnable implements Runnable{
public void run(){
//do something here
}
}
Runnable oneRunnable=new SomeRunnable();
Thread oneThread=new Thread(oneRunnable);
oneThread.start();
(3)通过Callable和Future创建线程
通过Callable和Future创建线程的具体步骤和具体代码如下:
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
使用FutureTask对象作为Thread对象的target创建并启动新线程。
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值其中,Callable接口(也只有一个方法)定义如下:
public interface Callable{
V call()throws Exception;
}
步骤1:创建实现Callable接口的类SomeCallable(略);
步骤2:创建一个类对象:
Callable oneCallable=new SomeCallable();
步骤3:由Callable创建一个FutureTask对象:
FutureTask oneTask=new FutureTask(oneCallable);
注释:FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。
步骤4:由FutureTask创建一个Thread对象:
Thread oneThread=new Thread(oneTask);
步骤5:启动线程:
oneThread.start();
3、多线程的优势
多线程的出现带来很多的好处:
(1)发挥多核处理器的性能:在多喝处理器上执行单线程任务是对多核的浪费,因为总有核心在空闲着,多线程的出现能充分发挥多核的优势。
(2)化整为零:往往在一个复杂的应用中包含许多不同类型的任务,将这些不同类型的任务分配给不同的线程去执行会比将其混在同一个线程中去执行要好,因为每个线程更加的简单清晰,更容易测试等。
(3)异步事件处理:当一个线程处理的任务遇到阻塞时如IO阻塞,cpu可以调度其他线程去执行而不是在那傻傻的等到IO结束在执行其他任务。
(5)更好的用户体验:当多个用户像你的服务发送请求时,你一个线程依次执行任务会使得排在后面的用户等待时间过长得不到响应,带来不好的体验。但使用多个线程可以让每个用户都能很快的得到响应(尽管这不能高执行速度),用户会觉得自己的请求正在被处理,获得更好的体验。
4、多线程的生命周期
(1)新建状态
用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。
注意:不能对已经启动的线程再次调用start()方法,否则会出现Java.lang.IllegalThreadStateException异常。
(2)就绪状态
处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列。因为cpu的调度不一定是按照先进先出的顺序来调度的),等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
提示:如果希望子线程调用start()方法后立即执行,可以使用Thread.sleep()方式使主线程睡眠一伙儿,转去执行子线程。
(3)运行状态
处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
处于就绪状态的线程,如果获得了cpu的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。如果该线程失去了cpu资源,就会又从运行状态变为就绪状态。重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出cpu资源,再次变为就绪状态。
注:当发生如下情况是,线程会从运行状态变为阻塞状态:
①、线程调用sleep方法主动放弃所占用的系统资源
②、线程调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
③、线程试图获得一个同步监视器,但更改同步监视器正被其他线程所持有
④、线程在等待某个通知(notify)
⑤、程序调用了线程的suspend方法将线程挂起。不过该方法容易导致死锁,所以程序应该尽量避免使用该方法。
当线程的run()方法执行完,或者被强制性地终止,例如出现异常,或者调用了stop()、desyory()方法等等,就会从运行状态转变为死亡状态。
(4)阻塞状态
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。有三种方法可以暂停Threads执行:
(5)死亡状态
当线程的run()方法执行完,或者被强制性地终止,就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
以上就是Java多线程的基础知识讲解,大家都了解了吗?这些基础知识大家都必须掌握,如果还有什么不明白的,可以报java培训的Java课程,会有线上的专业老师为大家答疑解惑。