java核心(十二):多线程(第一篇)

一、多线程的实现方式

Java多线程实现方式主要有三种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程。

其中前两种方式线程执行完后都没有返回值,后一种是带返回值的。

  1、第一种实现方式:继承Thread类

  • 继承Java.lang.Thread类,重写run()方法,将多线程代码块写入到run()方法中。
  • 启动线程方式:实例化本类对象,然后调用start()方法。
/**
 * 继承Thread类,重写run()方法
 */
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this.getName()+" , i = "+i); //getName(),获取当前线程的名称
        }
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        myThread.start();     //Thread-0
        myThread1.start();    //Thread-1
    }
}
//程序运行结果:
Thread-0 , i = 0
Thread-1 , i = 0
Thread-1 , i = 1
Thread-0 , i = 1
Thread-0 , i = 2
Thread-0 , i = 3
Thread-0 , i = 4
Thread-1 , i = 2
Thread-1 , i = 3
Thread-1 , i = 4

  2、第二种实现方式:实现Runnable接口

  • 实现Java.lang.Runnable接口,并重写run()方法,将多线程代码块写入到run()方法中。
  • 启动线程方式:将本类对象作为参数传入给Thread的构造方法,来实例化Thread对象,然后调用Thread对象的start()方法。
/**
 * 实现Runnable接口,重写run()方法
 */
class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this+" , i = "+i); 
        }
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        Runnable myThread = new MyThread();
        Runnable myThread1 = new MyThread();
        new Thread(myThread).start();
        new Thread(myThread1).start();
    }
}
//注:Thread类的构造方法,可以接收Runnable实例对象
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

  3、第三种实现方式:实现Callable接口

  • 实现java.util.concurrent.Callable接口,并重写call()方法,将多线程代码块写入到call()方法中。
  • 启动线程方式:(1)创建Callable接口实例;(2)然后,创建FutureTask类的实例,并将Callable接口的实例作为参数传入FutureTask实例中;(3)最后,将FutureTask实例作为参数传入给Thread的构造方法,来实例化Thread对象,并调用Thread对象的start()方法,启动多线程。(4)另外,线程结束后,可以通过FutureTask对象的get()方法,获取方法call()方法的返回值。
  • 注:FutureTask<V>是一个包装器,它通过接受Callable<V>来创建,它同时实现了Future和Runnable接口。
/**
 * 实现Callable接口,重写call()方法
 */
class MyThread implements Callable {
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println(this+" , i = "+i);
        }
        return this + " 线程结束";
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        //创建Callable实例
        Callable<String> myThread = new MyThread();
        Callable<String> myThread1 = new MyThread();
        //由Callable实例,来创建一个FutureTask<String>实例
        //注释:FutureTask<V>是一个包装器,它通过接受Callable<V>来创建,它同时实现了Future和Runnable接口。
        FutureTask<String> myTask = new FutureTask<>(myThread);
        FutureTask<String> myTask1 = new FutureTask<>(myThread1);
        //创建一个Thread对象,并调用start()方法来启动线程
        new Thread(myTask).start();
        new Thread(myTask1).start();
        //如有必要,等待计算完成,然后检索其结果
        System.out.println(myTask.get());
        System.out.println(myTask1.get());
    }
}
//程序运行结果:
com.study.grammar.MyThread@56c2b49a , i = 0
com.study.grammar.MyThread@762e2323 , i = 0
com.study.grammar.MyThread@56c2b49a , i = 1
com.study.grammar.MyThread@56c2b49a , i = 2
com.study.grammar.MyThread@762e2323 , i = 1
com.study.grammar.MyThread@56c2b49a , i = 3
com.study.grammar.MyThread@762e2323 , i = 2
com.study.grammar.MyThread@56c2b49a , i = 4
com.study.grammar.MyThread@762e2323 , i = 3
com.study.grammar.MyThread@56c2b49a 线程结束
com.study.grammar.MyThread@762e2323 , i = 4
com.study.grammar.MyThread@762e2323 线程结束

 二、Thread类启动线程的底层实现

  Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法会调用本类的start0()方法,start0()方法是一个native方法,它将启动一个新线程,并执行run()方法。
 
     
/**
 * Thread类的start0()方法
* Java开发里面有一门技术成为JNI技术(Java Native Interface),这门技术的特点是:使用Java来调用本机操作系统提供的函数。但是这样以来就存在一个缺点,即:不能离开特定的操作系统。
* 如果要想线程能够执行,严格来讲是由操作系统来启动线程,并分配系统资源的。
* 即:使用Thread类的start0()方法,不仅仅包括启动线程的执行代码,而且还包括调用操作系统函数进行资源的分配。
 */
private native void start0();
/**
 * Thread类的start()方法
 */
public synchronized void start() {
    //此异常属于RuntimeException的子类,属于选择性异常。
    if (threadStatus != 0)    
        throw new IllegalThreadStateException();
    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

三、Callable接口、Runnable接口,两者的区别

  • 实现多线程时,Runnable接口的方法是run();Callable接口的方法是call()。
  • 实现Runnable接口,任务没有返回值;实现Callable接口,任务可以设置返回值。
  • Runnable无法抛出经过检查的异常,而Callable可以抛出经过检查的异常。
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
Callable接口,返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
Callable接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable不会返回结果,并且无法抛出经过检查的异常。
Executors 类包含一些从其他普通形式转换成 Callable 类的实用方法。

四、使用ExecutorService、Callable、Future实现有返回结果的线程

  ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。而且自己实现了也可能漏洞百出。
  执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
  注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
  下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:
import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;

/**
 * 通过实现Callable接口,实现有返回值的线程
 */
class MyCallable implements Callable<Object> {
    private String taskNum;

    MyCallable(String taskNum) {
        this.taskNum = taskNum;
    }

    /**
     * 重写多线程方法,
     */
    public Object call() throws Exception {
        System.out.println(">>>" + taskNum + "任务启动");
        Date dateTmp1 = new Date();
        Thread.sleep(1000);
        Date dateTmp2 = new Date();
        long time = dateTmp2.getTime() - dateTmp1.getTime();
        System.out.println(">>>" + taskNum + "任务终止");
        return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
    }
}

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("----程序开始运行----");
        int taskSize = 5;
        // 创建一个线程池
        ExecutorService pool = Executors.newFixedThreadPool(taskSize);
        // 创建多个有返回值的任务
        List<Future> list = new ArrayList<Future>();
        for (int i = 0; i < taskSize; i++) {
            Callable c = new MyCallable(i + " ");
            // 执行任务并获取Future对象
            Future f = pool.submit(c);  //ExecutorService.submit(),提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
            list.add(f);
        }
        // 关闭线程池
        pool.shutdown();

        // 获取所有并发任务的运行结果
        for (Future f : list) {
            // 从Future对象上获取任务的返回值,并输出到控制台
            System.out.println(">>>" + f.get().toString());
        }
    }
}
//程序运行结果
----程序开始运行----
>>>0 任务启动
>>>1 任务启动
>>>2 任务启动
>>>3 任务启动
>>>4 任务启动
>>>4 任务终止
>>>1 任务终止
>>>0 任务终止
>>>3 任务终止
>>>0 任务返回运行结果,当前任务时间【1005毫秒】
>>>1 任务返回运行结果,当前任务时间【1005毫秒】
>>>2 任务终止
>>>2 任务返回运行结果,当前任务时间【1005毫秒】
>>>3 任务返回运行结果,当前任务时间【1005毫秒】
>>>4 任务返回运行结果,当前任务时间【1005毫秒】
代码说明:
上述代码中Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads) 
创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool() 
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor() 
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。

猜你喜欢

转载自www.cnblogs.com/newbie27/p/10551705.html