JUC并发编程基础(2)--Java中线程的实现

Java中线程的实现

实现方式
  • 继承Thread类,重写run()方法;

    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()方法。

  • 实现Runnable接口的run方法;

    public static class MyThread2 implements Runnable{
    
            @Override
            public void run() {
                System.out.println("MyThread 2!");
            }
        }
        public static void main(String[] args)
        {
            Thread myThread = new MyThread();
            myThread.start();
    
            MyThread2 myThread2 = new MyThread2();
            //myThread2.start(); Runnable接口没有start方法
            new Thread(myThread2).start();
        }
    
Thread类源码阅读
// Thread类源码 

// 片段1 - init方法
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals)

// 片段2 - 构造函数调用init方法
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

// 片段3 - 使用在init方法里初始化AccessControlContext类型的私有属性
this.inheritedAccessControlContext = 
    acc != null ? acc : AccessController.getContext();

// 片段4 - 两个对用于支持ThreadLocal的私有属性
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  • g:线程组,指定这个线程是在哪个线程组下;
  • target:指定要执行的任务;
  • name:线程的名字,多个线程的名字是可以重复的。如果不指定名字,见片段2;
  • acc:见片段3,用于初始化私有变量inheritedAccessControlContext

疑问:inheritedAccessControlContext的作用是什么?

  • inheritThreadLocals:可继承的ThreadLocal,见片段4,Thread类里面有两个私有属性来支持ThreadLocal

通常情况下,一般是这样调用两个构造方法:

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

  • 实现接口的方法更符合面向对象的思想,继承Thread每一个线程都是Thread对象,但实现接口就是单独的线程对象。

  • Runnable降低了线程对象和线程任务的耦合性。

    • 简单来说就是将设置对象和启动线程两个任务分离
  • 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。

Callable、Future与FutureTask

使用场景:使用以上两种方式创建线程,有一个弊端,就是run方法是没有返回值的,当我们开启一个线程去完成任务并且希望这个任务完成之后能有一个返回值,那么此时Callable和Future就有用了。

Callable接口

总结一下使用Callable获得返回值的步骤:

  • 自定义任务(线程)类实现Callable接口,并实现对应的call()方法;
  • call方法是可以接受泛型的,也就是可以返回一个泛型类型的值。
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
  • 在需要用到这个任务线程的时候,需要通过ExecutorService对象进行一个submit()。
  • 提交之后才能拿到返回的一个Future对象,再通过这个对象的get方法拿到任务线程的返回值。
// 自定义Callable
class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        // 模拟计算需要一秒
        Thread.sleep(1000);
        return 2;
    }
    public static void main(String args[]) throws Exception {
        // 使用
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executor.submit(task);
        // 注意调用get方法会阻塞当前线程,直到得到结果。
        // 所以实际编码中建议使用可以设置超时时间的重载get方法。
        System.out.println(result.get()); 
    }
}

输出结果:2
Future接口

其中的一些方法

public abstract interface Future<V> {
    public abstract boolean cancel(boolean paramBoolean);
    public abstract boolean isCancelled();
    public abstract boolean isDone();
    public abstract V get() throws InterruptedException, ExecutionException;
    public abstract V get(long paramLong, TimeUnit paramTimeUnit)
            throws InterruptedException, ExecutionException, TimeoutException;
}

cancel方法是试图取消一个线程的执行。

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

所以有时候,为了让任务有能够取消的功能,就使用Callable来代替Runnable。如果为了可取消性而使用 Future但又不提供可用的结果,则可以声明 Future<?>形式类型、并返回 null作为底层任务的结果。

FutureTask接口

上面介绍了Future接口。这个接口有一个实现类叫FutureTaskFutureTask是实现的RunnableFuture接口的,而RunnableFuture接口同时继承了Runnable接口和Future接口:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

FutureTask类有什么用?为什么要有一个FutureTask类?前面说到了Future只是一个接口,而它里面的cancelgetisDone等方法要自己实现起来都是非常复杂的。所以JDK提供了一个FutureTask类来供我们使用。

示例代码:

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

使用上与第一个Demo有一点小的区别。首先,调用submit方法是没有返回值的。这里实际上是调用的submit(Runnable task)方法,而上面的Demo,调用的是submit(Callable<T> task)方法。

然后,这里是使用FutureTask直接取get取值,而上面的Demo是通过submit方法返回的Future去取值。

在很多高并发的环境下,有可能Callable和FutureTask会创建多次。FutureTask能够在高并发环境下确保任务只执行一次

FutureTask的几个状态
/**
  *
  * state可能的状态转变路径如下:
  * NEW -> COMPLETING -> NORMAL
  * NEW -> COMPLETING -> EXCEPTIONAL
  * NEW -> CANCELLED
  * NEW -> INTERRUPTING -> INTERRUPTED
  */
private volatile int state;
private static final int NEW          = 0;
private static final int COMPLETING   = 1;
private static final int NORMAL       = 2;
private static final int EXCEPTIONAL  = 3;
private static final int CANCELLED    = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED  = 6;

state表示任务的运行状态,初始状态为NEW。运行状态只会在set、setException、cancel方法中终止。COMPLETING、INTERRUPTING是任务完成后的瞬时状态。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值