java 线程的创建和使用

177 阅读5分钟

Thread类和Runnable接口

JDK中提供Tread类和Runnable接口供我们创建线程类实现多线程功能。
继承Tread类,重写Run方法;实现Runnable接口的run方法。

Thread类

public class Demo {
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
        Thread myThread = new MyThread();
        myThread.start();
    }
}

这里启动线程需要调用start方法,而不是run方法。run方法只是线程执行的功能,而start方法才是启动一个线程。

当我们调用start方法后,虚拟机会先为我们创建线程,然后等到该线程分配到时间片后再调用run方法。第一次调用start方法后再次调用会抛出异常。

Runnable接口

runnable接口内部就run这一个方法

public interface Runnable {

    public abstract void run();
}

实现

public class Demo {

    public static  class  Mythread implements Runnable{

        @Override
        public void run() {
            System.out.println("继承接口实现");
        }
    }

    public static void main(String[] args) {
        Mythread my = new Mythread();
        Thread thread1 = new Thread(my);
        thread1.start();
    }
}

Threa的构造方法

从上面可以发现我实现runnable接口时将该类对象放入到了Tthread类中,调用了Thread类的构造方法。下面我将说一点Thread的源码。

//Threa类继承了 Runnable接口
class Thread implements Runnable

几个构造方法

//上面用到的构造方法
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

image.png

这里的构造器都调用了init方法来进行初始化。

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals)

参数:

  1. g:线程组
  2. target:要执行的任务
  3. name:线程的名字
  4. acc:用于初始化私有变量inheritedAccessControlContext
  5. inheritThreadLocals:可继承的ThreadLocal

Thread类的方法

  • currentThread():静态方法,返回对当前正在执行的线程对象的引用;
  • start():开始执行线程的方法,java虚拟机会调用线程内的run()方法;
  • yield():yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。这里需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的;
  • sleep():静态方法,使当前线程睡眠一段时间;
  • join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的;

Runnable接口和TThread类的比较

实现一个线程可以使用Runnable和Thread,他们的具体差别如下:

  1. 由于java中单继承多实现的特性,使用Runnable接口更灵活
  2. 使用接口实现更符合java面向对象的特征,这样会使线程的功能和线程都创建对象。
  3. 这样做也会降低线程对象和线程任务间的耦合。
  4. 如果使用线程不需要使用Thread的多种方法,那么使用Runnable接口更为轻量。

Callable、Future与FutureTask

上面提到的创建使用线程的方法有一个问题,那就是这么做会没有返回值,为了解决这个问题,jdk为我们提供了Callable接口与Future类。

Callable接口

public interface Callable<V> {
 
    V call() throws Exception;
}

不仅有返回值而且还支持泛型。

Future接口

  • Future接口用来表示异步执行的结果,比如异步执行一个Callable任务就会返回一个Future对象,通过这个Future对象来获取异步执行的结果
public interface Future<V> {
    /**
     * 1.尝试取消任务,如果任务已经完成或者被取消则会取消失败,也有可能因为其他原因取消失败,方法返回true表示取消成功,
     *  false表示取消失败,通常是因为任务已经开始了
     * 2.如果任务还未开始并且被取消成功,那么这个任务再也不会被执行
     * 3.如果任务已经开始,mayInterruptIfRunning参数决定执行该任务的线程是否需要中断任务,true表示正在执行任务的线程需要中断,false表示既然已经开始执行了,就执行完毕吧
     * 4.如果方法返回true,那么isDone和isCancelled也会返回true
     */
    boolean cancel(boolean mayInterruptIfRunning);

    //如果任务在完成之前被取消,返回true
    boolean isCancelled();

    //如果任务已经完成,返回true,这里的完成不仅仅是代表正常完成,也有可能是异常终止,取消,这些都会返回true
    boolean isDone();

    //阻塞获取异步执行的结果
    V get() throws InterruptedException, ExecutionException;

    //指定超时时间的get超时
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

cancel是试图取消,并不一定能取消成功。因为任务可能已完成、已取消、或者一些其它因素不能取消,存在取消失败的可能。boolean类型的返回值是“是否取消成功”的意思。参数paramBoolean表示是否采用中断的方式取消线程执行。

FutureTask类

FutureTask类实现了RunnableFuture类,而RunnableFuture类又继承自Runnable和Future

public class FutureTask<V> implements RunnableFuture<V> 
public interface RunnableFuture<V> extends Runnable, Future<V>

因为Future中一些方法的具体实现很复杂,所以jdk为我们提供了一个实现类。

具体实现

通过callable实现多线程,我们可以配合线程池工具ExecutorService来使用。ExecutorService可以使用submit方法来让一个Callable接口执行。它会返回一个Future,我们后续的程序可以通过这个Futureget方法得到结果。

// 自定义Callable
class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        // 模拟计算需要一秒
        Thread.sleep(1000);
        return 2;
    }
    public static void main(String args[]){
        // 使用
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executor.submit(task);
        // 注意调用get方法会阻塞当前线程,直到得到结果。
        // 所以实际编码中建议使用可以设置超时时间的重载get方法。
        System.out.println(result.get()); 
    }
}

通过FutureTask实现多线程

// 自定义Callable,与上面一样
class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        // 模拟计算需要一秒
        Thread.sleep(1000);
        return 2;
    }
    public static void main(String args[]){
        // 使用
        ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask<Integer> futureTask = new FutureTask<>(new Task());
        executor.submit(futureTask);
        System.out.println(futureTask.get());
    }
}

(42条消息) 17-Future接口_惑边的博客-CSDN博客

2 Java多线程入门类和接口 - 深入浅出Java多线程 (gitbook.io)