多线程创建

多线程创建

一、获得多线程的方法有几种?

共有四种。传统的是继承thread类和实现runnable接口,java5以后又有实现callable接口和java的线程池获得。
在这里插入图片描述

二、继承Thread类

1、步骤

	1. 自定义一个类继承Thread类。
	2. 重写Thread类的run方法 , 把自定义线程的任务代码写在run方法中
		
	3. 创建Thread的子类对象,并且调用start方法开启线程。

疑问: 重写run方法的目的是什么?

每个线程都有自己的任务代码,jvm创建的主线程的任务代码就是main方法中的所有代码, 自定义线程的任务代码就写在run方法中,自定义线程负责了run方法中代码。

在这里插入图片描述
在这里插入图片描述

2、注意:
一个线程一旦开启,那么线程就会执行run方法中的代码,run方法千万不能直接调用,直接调用run方法就相当调用了一个普通的方法而已并没有开启新的线程。
原因:
查看start方法源码,

Start(){.
      start0(); //是一个本地方法,其调用了run方法。
}

这是一种模板设计模式,类似netty中的继承一些入栈handler,重写读和写方法一样。

public void run() {
        if (target != null) {
            target.run();        
        }
    }

注意:这里的target是Thread维护的一个Runnable接口的引用,Runnable target.

如果从构造函数中传来了Runnable接口的实现类,那么target会引用到这个实现类,在run方法中调用了实现类的run。

如果没有传入runnable,那么就要重写这个run方法,由JVM调用。

三、实现runnable接口

注意:runnable不是线程,线程只有Thread。runnable只是一个接口 ,可以把线程的任务单独提出来。runnable将可执行的逻辑单元和线程控制分离开来,这也是面向对象思想的体现。(将Runnable实现类对象定义为final,即多个线程操控的是同一个Runnable实现类对象,同一份业务单元,当然,这里也会存在线程安全问题,后续再讲)
在这里插入图片描述
在这里插入图片描述
1、步骤

	1. 自定义一个类实现Runnable接口。
	2. 实现Runnable接口(只有一个run方法)的run方法,把自定义线程的任务定义在run方法上。
	3. 创建Runnable实现类对象。
	4. 创建Thread类的对象,并且把Runnable实现类的对象作为实参传递。
	5. 调用Thread对象 的start方法开启一个线程。
public class TestDemo implements Runnable {
    public void run() {
       
    }
 
    public static void main(String[] args) {
        new Thread(new TestDemo()).start();
    }
}

四、实现Callable接口,本质还是runnable

1、步骤

	1.自定义一个类实现callable接口
	2.实现call方法
	3.创建futuretask对象,把自定义类当做构造参数传进去
	4.创建一个线程 new thread,把futuretask当做构造参数传进去。
	5.调用futuretask的run方法。
 public void run() {..
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
               执行完毕后,把返回值set到result中。
                    set(result);
            }
        } finally {..
        }
    }
class MyThread implements Runnable{
    @Override
    public void run() {

    }
}

class MyThread2 implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("******come in call method()");
        return 1080;
    }
}

/**
 * 多线程中,第3种获得多线程的方式
 */
public class CallableDemo7 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask(new MyThread2());

        new Thread(futureTask,"A").start();
		//get方法放到最后,否则会阻塞主线程的执行,影响效率
        Integer result = futureTask.get();//获得call()方法的返回值
        System.out.println(result);
    }
}

Thread的构建只有利用Runnable接口,因此需要Runnable接口和Callable接口的“中间人”,即FutureTask类,实现了Runnable的子接口,同时可以接受Callable接口实现类的形参。因此,即可使用Callable接口生成线程。本质上还是利用了Java的多态性质。
在这里插入图片描述
在这里插入图片描述

FutureTask<Integer> ft = new FutureTask<Integer>(new MyThread());
new Thread(ft, "AA").start();

运行成功后如何获得返回值?

ft.get();

FutureTask实现了callable和ruannable接口,其run方法本质还是runnable的run方法。thread调用的是futuretask的run方法。只不过futuretask封装了返回值以及一些超时方法。

五、线程池(略)

六、Runnable与Callable接口的区别

创建新类MyThread实现runnable接口
class MyThread implements Runnable{
 @Override
 public void run() {
 
 }
}
新类MyThread2实现callable接口
class MyThread2 implements Callable<Integer>{
 @Override
 public Integer call() throws Exception {
  return 200;
 } 
}

面试题:runnable接口与callable接口的区别?

  • 落地方法不一样:一个是run,一个是call
  • 是否有返回值:前者run()方法无返回值,后者call()方法有返回值
  • 是否抛异常:前者没有异常,后者需要处理异常

七、new Thread()与Runnable接口建立线程的区别

Thread本来就是实现了Runnable,包含Runnable的功能是很正常的啊!!至于两者的真正区别最主要的就是一个是继承,一个是实现;其他还有一些面向对象的思想,Runnable就相当于一个作业,而Thread才是真正的处理线程,我们需要的只是定义这个作业,然后将作业交给线程去处理,这样就达到了松耦合,也符合面向对象里面组合的使用,另外也节省了函数开销,继承Thread的同时,不仅拥有了作业的方法run(),还继承了其他所有的方法。综合来看,用Runnable比Thread好的多。

runnable接口的作用本身是一种策略模式的体现。

八、FutureTask

FutureTask的异步调用

在这里插入图片描述
FutureTask,用它就干一件事,异步调用。
main方法就像一个冰糖葫芦,一个个方法由main串起来。但解决不了一个问题:正常调用挂起堵塞问题。

例子:
(1)老师上着课,口渴了,去买水不合适,讲课线程继续,我可以单起个线程找班长帮忙买水,
水买回来了放桌上,我需要的时候再去get。
(2)4个同学,A算1+20,B算21+30,C算31*到40,D算41+50,是不是C的计算量有点大啊,
FutureTask单起个线程给C计算,我先汇总ABD,最后等C计算完了再汇总C,拿到最终结果
(3)高考:会做的先做,不会的放在后面做

FutureTask原理

在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。

一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。因此get方法常放到最后。

仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。

注意:
只计算一次,缓存上次结果
get方法放到最后,否则会阻塞主线程的执行,影响效率

代码

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

class MyThread implements Runnable{

    @Override
    public void run() {

    }
}
class MyThread2 implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"come in callable");
        return 200;
    }
}


public class CallableDemo {


    public static void main(String[] args) throws Exception {

        //FutureTask<Integer> futureTask = new FutureTask(new MyThread2());
        FutureTask<Integer> futureTask = new FutureTask(()->{
            System.out.println(Thread.currentThread().getName()+"  come in callable");
            TimeUnit.SECONDS.sleep(4);
            return 1024;
        });
        FutureTask<Integer> futureTask2 = new FutureTask(()->{
            System.out.println(Thread.currentThread().getName()+"  come in callable");
            TimeUnit.SECONDS.sleep(4);
            return 2048;
        });

        new Thread(futureTask,"zhang3").start();
        new Thread(futureTask2,"li4").start();

        //System.out.println(futureTask.get());
        //System.out.println(futureTask2.get());
        //1、一般放在程序后面,直接获取结果
        //2、只会计算结果一次

        while(!futureTask.isDone()){
            System.out.println("***wait");
        }
        System.out.println(futureTask.get());
        System.out.println(Thread.currentThread().getName()+" come over");
    }
}
 
/**
 * 
 * 
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,
当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
 
一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
 
仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,
就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,
然后会返回结果或者抛出异常。 
 
只计算一次
get方法放到最后
 */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值