JUC(四)——线程的创建

x33g5p2x  于2021-09-24 转载在 其他  
字(5.2k)|赞(0)|评价(0)|浏览(287)

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,

一、继承Thread类

通过继承Thread类来创建并启动多线程的一般步骤如下:
1、定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
2、创建Thread子类的实例,也就是创建了线程对象。
3、启动线程,即调用线程的start()方法。

public class MyThreadTest1 {
    public static void main(String[] args) {
        Thread thread1 = new MyThread1("线程1");
        Thread thread2 = new MyThread2("线程2");
        thread1.start();
        thread2.start();

    }
    // //继承Thread类
    static class MyThread1 extends Thread {
        public MyThread1(String name) {
            super(name);
        }
        // 重写run方法
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "-------第" + i + "次执行");
            }
        }
    }
    static class MyThread2 extends Thread {
        public MyThread2(String name) {
            super(name);
        }

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "-------第" + i + "次执行");
            }
        }
    }
}

二、实现Runnable接口

通过实现Runnable接口创建并启动线程一般步骤如下:
1、定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体。
2、创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象。
3、通过调用线程对象的start()方法来启动线程。

public class MyThreadTest2 {
    public static void main(String[] args) {
        new Thread(new MyRunnable1(), "线程1").start();
        new Thread(new MyRunnable2(), "线程2").start();
    }

    static class MyRunnable1 implements Runnable {
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-------第" + i + "次执行");
            }
        }
    }

    static class MyRunnable2 implements Runnable {
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-------第" + i + "次执行");
            }
        }
    }
}

三、实现Callable接口

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大,call()方法可以有返回值,call()方法可以声明抛出异常。

1、Callable创建线程的原理

执行 Callable 方式,需要 FutureTask实现类的支持,用于接收运算结果。FutureTask 是 Future 接口的实现类。
Callable接口是JAVA新增的接口,而且它不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target。还有一个原因就是:call()方法有返回值,call()方法不是直接调用,而是作为线程执行体被调用的,所以这里涉及获取call()方法返回值的问题。

于是,JAVA5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现了Future接口,并实现了Runnable接口,所以FutureTask可以作为Thread类的target,同时也解决了Callable对象不能作为Thread类的target这一问题。

继承关系:

public class FutureTask<V> implements RunnableFuture<V> {
    /* * Revision notes: This differs from previous versions of this * class that relied on AbstractQueuedSynchronizer, mainly to * avoid surprising users about retaining interrupt status during * cancellation races. Sync control in the current design relies * on a "state" field updated via CAS to track completion, along * with a simple Treiber stack to hold waiting threads. * * Style note: As usual, we bypass overhead of using * AtomicXFieldUpdaters and instead directly use Unsafe intrinsics. */
     }
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /** * Sets this Future to the result of its computation * unless it has been cancelled. */
    void run();
}

在Future接口里定义了几个公共方法来控制它关联的Callable任务:

public interface Future<V> {

    /** * 视图取消该Future里面关联的Callable任务 */
    boolean cancel(boolean mayInterruptIfRunning);

    /** * 如果在Callable任务正常完成前被取消,返回True */
    boolean isCancelled();

    /** * 若Callable任务完成,返回True */
    boolean isDone();

    /** * 返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值 */
    V get() throws InterruptedException, ExecutionException;

    /** * 返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

2、创建步骤
  • (1)创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例。
  • (2)使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。
  • (3)用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口。
  • (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
public class MyCallableTest {
    public static void main(String[] args){
        // 2、执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
        // 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable1());
        // 3、使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
        new Thread(futureTask).start();
        Integer integer = null;
        try {
            // 4、调用FutureTask对象的get()方法来接受子线程执行结束后的返回值,所有的线程没有执行完成之后这里是不会执行的
            integer = futureTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(integer);

    }

    // 1、创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例。
    static class MyCallable1 implements Callable<Integer> {
        // 方法的返回值类型与上面Callable后面的泛型一致
        public Integer call() throws Exception {
            int sum = 0;
            for (int i = 0; i < 10; i++) {
                sum += i;
            }
            return sum;
        }
    }
}

四、通过线程池创建

线程池提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提交了响应速度。线程池创建线程我们后面详细讲解,这里暂时不深入展开。

五、三种创建线程方法对比

上面已经介绍完了JAVA中创建线程的三种方法,通过对比我们可以知道,JAVA实现多线程可以分为两类:一类是继承Thread类实现多线程;另一类是:通过实现Runnable接口或者Callable接口实现多线程。

下面我们来分析一下这两类实现多线程的方式的优劣:

1、继承Thread和实现Runnable
  • 由于java中的单继承的限制,一个类只能继承一个类但是可以实现多个接口,所有更推荐实现Runnable接口的创建线程的方式。
  • 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类,所以在实际工作中通常使用的是实现Runnable接口的创建线程的方式。
  • 通过实现Runnable接口,多个线程可以使用同一个target对象,适合多个线程处理同一份资源的情况。
2、实现Runnable和实现Callable
  • Callable规定的方法是call(),而Runnable规定的方法是run()。
  • Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
  • call()方法可抛出异常,而run()方法是不能抛出异常的。
  • 运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。
  • 通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
  • Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。

相关文章