Java多线程学习(二)---线程建立方式

线程建立方式

摘要编程

1. 经过继承Thread类来建立并启动多线程的方式安全

2. 经过实现Runnable接口来建立并启动线程的方式多线程

3. 经过实现Callable接口来建立并启动线程的方式this

4. 总结Java中建立线程的方式,比较各自优点和区别spa

1、继承Thread类建立线程类

1.1 继承Thread类建立线程步骤

Java使用Thread类表明线程全部的线程对象都必须是Thread类或其子类实例每一个线程的做用是完成必定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来表明这段程序流。Java中经过继承Thread类来建立启动多线程的步骤以下:线程

01. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就表明了线程须要完成的任务对象

     所以把run()方法称为线程执行体blog

02. 建立Thread子类的实例,即建立了线程对象继承

03. 调用线程对象的start()方法来启动该线程接口

1.2 继承Thread类建立线程示例

下面程序示范了经过继承Thread类来建立并启动多线程:

  1. // 经过继承Thread类来建立线程类
  2. public class MyThreadTest extends Thread {
  3.     private int i;
  4.     // 重写 run方法,run方法的方法体就是线程执行体
  5.     public void run() {
  6.         for (; i < 100; i++) {
  7.             // 当线程类继承 Thread类时,直接使用this便可获取当前线程
  8.             // Thread 对象的 getName()返回当前该线程的名字
  9.             // 所以能够直接调用 getName()方法返回当前线程的名
  10.             System.out.println(getName() + "" + i);
  11.         }
  12.     }
  13.     public static void main(String[] args) {
  14.         for (int i = 0; i < 100; i++) {
  15.             // 调用 ThreadcurrentThread方法获取当前线程
  16.             System.out.println(Thread.currentThread().getName() + "" + i);
  17.             if (i == 20) {
  18.                 // 建立、并启动第一条线程
  19.                 new MyThreadTest().start();
  20.                 // 建立、并启动第二条线程
  21.                 new MyThreadTest().start();
  22.             }
  23.         }
  24.     }
  25. }

运行部分结果:

虽然上面程序只显式地建立并启动了2个线程,但实际上程序有3个线程,即程序显式建立的2个子线程1个主线程。前面已经提到,当Java程序开始运行后,程序至少会建立一个主线程,主线程的线程执行体不是由run()方法肯定的,而是由main()方法肯定的,main()方法的方法体表明主线程的线程执行体。

该程序不管被执行多少次输出的记录数是必定的,一共是300条记录。主线程会执行for循环打印100条记录,两个子线程分别打印100条记录,一共300条记录。由于i变量是MyThreadTest的实例属性,而不是局部变量,但由于程序每次建立线程对象时都须要建立一个MyThreadTest对象,因此Thread-0和Thread-1不能共享该实例属性,因此每一个线程都将执行100次循环。

2、实现Runnable接口建立线程类

2.1 实现Runnable接口建立线程步骤

实现Runnable接口来建立并启动多线程的步骤以下:

01. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体一样是该线程的线程执行体

02. 建立Runnable实现类的实例,并以此实例做为Thread的target来建立Thread对象,该Thread对象才是真正的线程对象

03. 调用线程对象的start()方法来启动线程

须要注意的是:Runnable对象仅仅做为Thread对象的targetRunnable实现类里包含的run()方法仅做为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

2.2实现Runnable接口建立线程示例

下面程序示范了经过实现Runnable接口建立线程步骤:

  1. public class MyRunnableTest implements Runnable {
  2.     private int i;
  3.     void print(){
  4.          System.out.println(Thread.currentThread().getName() + "" + i);
  5.     }
  6.     // run 方法一样是线程执行体
  7.     public void run() {
  8.         for (; i < 100; i++) {
  9.             // 当线程类实现 Runnable接口时,
  10.             // 若是想获取当前线程,只能用 Thread.currentThread()方法。
  11.             print();
  12.         }
  13.     }
  14.     public static void main(String[] args) {
  15.         for (int i = 0; i < 100; i++) {
  16.             System.out.println(Thread.currentThread().getName() + "" + i);
  17.             if (i == 20) {
  18.                 MyRunnableTest st = new MyRunnableTest();
  19.                 // 经过 new Thread(target , name)方法建立新线程
  20.                 new Thread(st, " 新线程-1 ").start();
  21.                 new Thread(st, " 新线程-2 ").start();
  22.             }
  23.         }
  24.     }
  25. }

运行部分结果:

从该运行结果中咱们能够看出,控制台上输出的内容是乱序的,并且每次结果不尽相同。这是由于:

01. 在这种方式下,程序所建立的Runnable对象只是线程的target,而多个线程能够共享同一个target。

02. 因此多个线程能够共享同一个线程类即线程的target类的实例属性。

03. 往控制台窗口print()输出的过程并非多线程安全的,在一个线程输出过程当中另外一个线程也能够输出。

为可以保证顺序输出,咱们能够对打印方法设置Synchronized,让每次只能有一个进程可以访问打印,代码以下:

  1. public class MyRunnableTest implements Runnable {
  2.     private int i;
  3.     synchronized void print(){
  4.          System.out.println(Thread.currentThread().getName() + "" + i);
  5.     }
  6.     // run 方法一样是线程执行体
  7.     public void run() {
  8.         for (; i < 100; i++) {
  9.             // 当线程类实 Runnable 接口时,
  10.             // 若是想获取当前线程,只能用 Thread.currentThread()方法。
  11.             print();
  12.         }
  13.     }
  14.     public static void main(String[] args) {
  15.         for (int i = 0; i < 100; i++) {
  16.             System.out.println(Thread.currentThread().getName() + "" + i);
  17.             if (i == 20) {
  18.                 MyRunnableTest st = new MyRunnableTest();
  19.                 // 经过 new Thread(target , name)方法建立新线程
  20.                 new Thread(st, " 新线程-1 ").start();
  21.                 new Thread(st, " 新线程-2 ").start();
  22.             }
  23.         }
  24.     }
  25. }

运行结果:

该运行结果,更加明显的证实了,在这种方式下,多个线程共享了 tartget类实例 的属性

3、使用Callable和Future建立线程

3.1 Callable和Future接口概述

3.1.1 callable接口概述

也许受此启发,从Java 5开始,Java提供了Callable接口,该接口怎么看都像是Runnable接口的加强版,Callable接口提供了一个call()方法能够做为线程执行体,但call()方法比run()方法功能更强大。

01. call()方法能够有返回值

02. call()方法能够声明抛出异常

所以咱们彻底能够提供一个Callable对象做为Thread的target,而该线程的线程执行体就是该Callable对象的call()方法。问题是:Callable接口是Java 5新增的接口,并且它不是Runnable接口的子接口,因此Callable对象不能直接做为Thread的target。并且call()方法还有一 个返回值-----call()方法并非直接调用,它是做为线程执行体被调用的。那么如何获取call()方法的返回值呢?

3.1.2 Future接口概述

Java 5提供了Future接口来表明Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口Runnable接口能够做为Thread类的target。在Future接口里定义了以下几个公共方法来控制它关联的Callable任务:

1. boolcan cancel(boolean maylnterruptltRunning):试图取消该Future里关联的Callable任务

2. V get():返回Callable任务里call()方法的返回值。调用该方法将致使程序阻塞,必须等到子线程结束后才会获得返回值

3. V get(long timeout,TimeUnit unit):返回Callable任务里call()方法的返回值。

    该方法让程序最多阻塞timeoutunit指定的时间,若是通过指定时间后Callable任务依然没有返回值,

    将会抛出TimeoutExccption异常

4. boolean isCancelled():若是在Callable任务正常完成前被取消,则返回true

5. boolean isDone():妇果Callable任务已完成,则返回true

注意:Callable接口有泛型限制,Callable接口里的泛型形参类型与call()方法返回值类型相同。

3.1.3 建立并启动有返回值的线程的步骤

01. 建立Callable接口的实现类,并实现call()方法,该cal()方法将做为线程执行体,且该call()方法有返回值

02. 建立Callable实现类的实例,使用FutureTask类来包装Callable对象

      该FutureTask对象封装了该Callable对象的call()方法的返回值

03. 使用FutureTask对象做为Thread对象的target建立并启动新线程

04. 调用FutureTask对象的get()方法来得到子线程执行结束后的返回值

3.2使用Callable和Future建立线程示例

下面程序示范了经过实现Callable接口建立线程步骤:

  1. public class MyCallableTest implements Callable<Integer>{
  2.     // 实现 call方法,做为线程执行体
  3.     public Integer call(){
  4.         int i = 0;
  5.         for ( ; i < 100 ; i++ ){
  6.             System.out.println(Thread.currentThread().getName()+ "\t" + i);
  7.         }
  8.         // call() 方法可以有返回值
  9.         return i;
  10.     }
  11.     public static void main(String[] args) {
  12.         // 建立 Callable对象
  13.         MyCallableTest myCallableTest = new MyCallableTest();
  14.         // 使用 FutureTask来包装Callable对象
  15.         FutureTask<Integer> task = new FutureTask<Integer>(myCallableTest);
  16.         for (int i = 0 ; i < 100 ; i++){
  17.             System.out.println(Thread.currentThread().getName()+ " \t" + i);
  18.             if (i == 20){
  19.                 // 实质仍是以 Callable对象来建立、并启动线程
  20.                 new Thread(task , "callable").start();
  21.             }
  22.         }
  23.         try{
  24.             // 获取线程返回值
  25.             System.out.println("callable 返回值: " + task.get());
  26.         }
  27.         catch (Exception ex){
  28.             ex.printStackTrace();
  29.         }
  30.     }
  31. }

运行上面程序,将看到主线程和call()方法所表明的线程交替执行的情形,程序最后还会输出call()方法的返回值:

上面程序中建立Callable实现类与建立Runnable实现类并无太大的差异,只是Callable的call()方法容许声明抛出异常, 并且容许带返回值。当主线程中当循环变量i等于20时,程序启动以FutureTask对象为target的线程。程序最后调用FutureTask对象 的get()方法来返回call()方法的返回值——该方法将致使主线程阻塞,直到call()方法结束并返回为止。

4、总结

经过如下三种途径能够实现多线程:

01. 继承Thread类

02. 实现Runnable接口

03. 实现Callable接口

不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,能够声明抛出异常而已。 所以能够将实现Runnable接口和实现Callable接口归为一种方式。这种方式与继承Thread方式之间的主要差异以下。

(1) 采用实现Runnable、Callable接口的方式建立多线程

线程类只是实现了Runnable接口或Callable接口,还能够继承其余类。

在这种方式下,多个线程能够共享同一个target对象,因此很是适合多个相同线程来处理同一份资源的状况,从而能够将CPU、代码和数据分开,造成清晰的模型,较好地体现了面向对象的思想。

劣势:编程稍稍复杂,若是须要访问当前线程,则必须使用Thread.currentThread()方法。

(2) 采用继承Thread类的方式建立多线程

劣势:由于线程类已经继承了Thread类,因此不能再继承其余父类

若是,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。

若是,您但愿更容易地发现个人新博客,不妨点击一下左下角的【关注我】。

若是,您对个人博客所讲述的内容有兴趣,请继续关注个人后续博客,我是【Sunddenly】。

本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利

相关文章
相关标签/搜索