并发编程基础一--继承Thread,实现Runnable,实现Callable和Future.

一.继承Thread的线程

1.定义Thread类的子类,并重写该类的run()方法,该run()方法就是代表了线程要完成的任务.因此run方法是线程执行体.
2.创建Thread子类的实例,就是创建了线程对象.
3.调用线程对象的start()方法来启动该线程.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package org.credo.thread;
 
/**
  * <p>Description:继承Thread类创建线程类. </p>
  * @author <a href="zhaoqianjava@qq.com">Credo</a>
  */
public class FirstThread extends Thread{
     private int i;
     
     /*
      * 重写run()方法,run方法的方法体就是线程的执行体.
      */
     public void run(){
         for (;i< 10 ;i++){
             //当线程类继承Thread类时,直接使用this即可获取当前线程
             //Thread对象的getName()返回当前线程的名字
             //因此可以直接调用getName()方法返回当前线程的名字
             System.out.println(getName()+ " " +i);
         }
     }
     
     public static void main(String[] args) {
         for ( int i= 0 ;i< 10 ;i++){
             //调用Thread的currentThread()方法获得当前线程
             System.out.println(Thread.currentThread().getName()+ " " +i);
             if (i== 2 ){
                 //创建并启动第一个线程,使用start方法启动线程.
                 new FirstThread().start();
                 //创建并启动第二个线程
                 new FirstThread().start();
             }
         }
     }
}
上面的程序使用到了线程的两个方法:
  • Thread.currentThread():currentThread()是Thread类的静态方法,该方法总是返回当前正在执行的线程对象.
  • getName():该方法是Thread的实例方法,该方法返回调用该方法的线程名字.
程序也可以通过setName去为线程设定名字.
我们看下线程的输出情况.
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
main 0
main 1
main 2
Thread- 0 0
Thread- 0 1
Thread- 0 2
Thread- 0 3
Thread- 0 4
Thread- 1 0
main 3
Thread- 1 1
Thread- 0 5
Thread- 1 2
main 4
Thread- 1 3
main 5
Thread- 0 6
main 6
Thread- 1 4
main 7
Thread- 0 7
main 8
Thread- 1 5
main 9
Thread- 0 8
Thread- 0 9
Thread- 1 6
Thread- 1 7
Thread- 1 8
Thread- 1 9
通过上面的输出可以看得出,3个线程输出的"i 变量"是不连续的.注意,i变量是FirstThread的实例属性,而不是局部变量.但因为程序每次创建线程对象时都需要创建一个FirstThread对象,所以他们不能共享该实例属性,i.

因此就可以有个结论:使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量.

二.实现Runnable接口创建线程类

实现Runnable接口来创建并启动多线程的步骤如下:
1.定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体.
2.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象.

示例代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package org.credo.thread;
 
public class FirstRunnable implements Runnable{
 
     private int i;
     
     public void run(){
         for (;i< 10 ;i++){
             //当线程类实现Runnable接口时,
             //如果想获取当前线程,只能用Thread.currentThread()方法.
             System.out.println(Thread.currentThread().getName()+ " " +i);
         }
     }
     
     public static void main(String[] args) {
         for ( int i= 0 ;i< 10 ;i++){
             System.out.println(Thread.currentThread().getName()+ " " +i);
             if (i== 2 ){
                 FirstRunnable fr= new FirstRunnable();
                 //通过new Thread(target,name)方法创建新线程
                 new Thread(fr, "newThread11" ).start();
                 new Thread(fr, "newThread22" ).start();
             }
         }
     }
}
输出让我有点额...不太理解,就那两个0....按理说应该是1,2线程都是0~9顺序的.为毛啊?有高手解释没?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
main 0
main 1
main 2
main 3
main 4
newThread11 0
newThread22 0
newThread22 2
main 5
main 6
main 7
main 8
main 9
newThread22 3
newThread22 4
newThread11 1
newThread11 6
newThread22 5
newThread11 7
newThread22 8
newThread11 9
按理来说,i应该是连续的,也就是采用runnable接口的方式创建的多个线程可以共享线程类的实例属性.这是因为在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程可以共享同一个target,所以多个线程可以共享同一个线程类(实际上应该是线程的target类)的实例属性.

三:使用Callable和Future创建线程

前面可以看到,通过实现Runnable接口创建多线程的时候,Thread类的作用就是把run()方法包装成线程执行体.
那么是否可以把任意方法都包装成线程执行体呢?
Java貌似不可以,但C#貌似可以.
因此从java5开始,java提供了Callable接口,该接口其实就是Runnable接口的增强版.Callable接口提供了一个call()方法可以作为线程执行体,但call方法比run()方法功能更强大.

  • call方法可以有返回值.
  • call方法可以声明抛出异常.

因此我们可以提供一个callable对象作为Thread的target.而该线程的线程执行体就是该Callable对象的call方法.但问题来了,callable是java5新增的一个并不是runnable接口的子接口,因此不能直接作为Thread的target,而且call方法还有一个返回值,call方法并不是直接调用.它是作为线程执行体被调用的.那么如果最后获取其值呢?

Java提供了Future接口来代表Callable接口里的call方法的返回值,并为future提供了一个FutureTask实现类.该类实现了Future接口,并且实现了Runnable接口--它可作为Thread类的target.

步骤基本如下:

1.创建Callable接口的实现类,并实现call()方法,该call方法将作为线程执行体.能抛异常,能有返回值.
2.创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值.
3.使用FutureTask对象作为Thread对象的target创建并启动线程.
4.调用FutureTask对象的get()方法获取子线程的返回值.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package org.credo.thread;
 
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
 
public class CallableAndFuture implements Callable<Integer>{
 
     public static void main(String[] args) {
         CallableAndFuture caf= new CallableAndFuture();
         FutureTask<Integer> future= new FutureTask<Integer>(caf);
         for ( int i= 0 ;i< 100 ;i++){
             System.out.println(Thread.currentThread().getName()+ "的I的值是:" +i);
             if (i== 20 ){
                 new Thread(future, "有返回值的线程" ).start();
             }
         }
         try {
             System.out.println( "子线程的返回值:" +future.get());
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 
     @Override
     public Integer call() throws Exception {
         int i= 0 ;
         for (;i< 100 ;i++){
             System.out.println(Thread.currentThread().getName()+ "的I的值为:" +i);
         }
         return i;
     }
 
}

总结:掌握主线,支线的API查阅也就知道了.一般情况下不去使用继承Thread.太蛋疼了那样.就用Runnable和callable.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值