java多线程学习 基础篇(二) Thread类和Runnable接口
一个线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。下图显示了一个线程完整的生命周期。
如果我们需要有一个“线程”类,JDK提供了Thread
类和Runnalble
接口来让我们实现自己的“线程”类。
-
继承Thread类,并重写run方法(注意:Thread类实现了Runnable接口)
-
实现Runnable接口的run方法
继承Thread类
1.首先创建一个任务类extends Thread类,因为Thread类实现了Runnable接口,所以自定义的任务类也实现了Runnable接口,重新run()方法,其中定义具体的任务代码或处理逻辑。
2.创建一个任务类对象,可以用Thread或者Runnable作为自定义的变量类型。
3.调用自定义对象的start()方法,启动一个线程。
1 public class ThreadDemo extends Thread{
2
3 @Override
4 public void run() {
5 System.out.println("my Thread");
6 }
7
8 public static void main(String[] args) {
9 Thread t1 = new ThreadDemo();
10 t1.start();
11 System.out.println("结束");
12 }
13 }
使用start()
方法启动一个线程,注意不能多次调用start()
方法,否则会抛出异常。
>>
结束
my Thread
实现runnable接口
1.定义一个任务类实现Runnable接口,实现Runnable接口中的run()方法(run()方法告知系统线程该如何运行),run()方法中定义具体的任务代码或处理逻辑。
2.定义了任务类后,为任务类创建一个任务对象。
3.任务必须在线程中执行,创建一个Thread类的对象,将前面创建的实现了Runnable接口的任务类对象作为参数传递给Tread类的构造方法。
4.调用Thread类对象的start()方法,启动一个线程。它会导致任务的run()方法被执行,当run()方法执行完毕,则线程就终止。
如:
1 public class RunnableDemo implements Runnable{
2 @Override
3 public void run() {
4 System.out.println("my Thread by implements Runnable");
5 }
6
7 public static void main(String[] args) {
8 RunnableDemo mt = new RunnableDemo();
9 Thread t = new Thread(mt);
10 t.start();
11 System.out.println("end");
12 }
13 }
调用start方法会启动一个线程,导致任务中的run方法被调用,run方法执行完毕则线程终止
>>
end
my Thread by implements Runnable
Thread类的currentThread()
方法
currentThread()
:静态方法,返回对当前正在执行的线程对象的引用
1 public static void main(String[] args) {
2 System.out.println(Thread.currentThread().getName());
3 }
main
,证明main方法正在被名字叫main的线程调用。 修改代码如下: 1 public class ThreadDemo2 extends Thread{
2 public ThreadDemo2() {
3 System.out.println("构造方法打印:" + Thread.currentThread().getName());
4 }
5
6 @Override
7 public void run() {
8 System.out.println("run方法打印:" + Thread.currentThread().getName());
9 }
10
11 public static void main(String[] args) {
12 Thread t1 = new ThreadDemo2();
13 t1.start();
14 }
15 }
输出结果如下,证明构造函数是被main线程调用的,而run()
方法是被名叫“Thread-0”调用的。
>>
构造方法打印:main
run方法打印:Thread-0
1 public class ThreadDemo2 extends Thread{
2 public ThreadDemo2() {
3 System.out.println("构造方法打印:" + Thread.currentThread().getName());
4 }
5
6 @Override
7 public void run() {
8 System.out.println("run方法打印:" + Thread.currentThread().getName());
9 }
10
11 public static void main(String[] args) {
12 Thread t1 = new ThreadDemo2();
13 t1.run();
14 }
15 }
>>
构造方法打印:main
run方法打印:main
解释:
对于start()方法,Thread用 start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
对于run()方法,Runnable run() 方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待 run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。总结:调用start方法方可启动线程,而run方法只是thread的一 个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。
总结
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run 的无参数方法。设计该接口的目的是为希望在活动时执行代码的对象提供一个公共协议。例如,Thread 类实现了Runnable。激活的意思是说某个线程已启动并且尚未停止。此外,Runnable 为非 Thread 子类的类提供了一种激活方式。通过实例化某个Thread 实例并将自身作为运行目标,就可以运行实现Runnable 的类。大多数情况下,如果只想重写run() 方法,而不重写其他 Thread 方法,那么应使用Runnable 接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。(推荐使用创建任务类,并实现Runnable接口,而不是继承Thread类)。
(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。
守护线程
线程的优先级
/** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10;
setPriority(int newPriority)
这个方法设置优先级,参数newPriority
越大,优先级越高。但是需要注意的是优先级虽然高,占得CPU资源较多,但是也不能保证优先级高的线程全部执行完,因为优先级具有随机性
。Thread类与Runnable接口的比较
-
由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
-
Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
-
Runnable接口出现,降低了线程对象和线程任务的耦合性。
-
如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。