JUC-线程创建的四种方式+原理分析

Posted 滑稽404#

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC-线程创建的四种方式+原理分析相关的知识,希望对你有一定的参考价值。

一、继承Thread

1、创建空线程

		Thread thread=new Thread();
        thread.start();

线程执行流程:start方法启动线程,并执行run方法,run方法为线程执行入口
Thread类里有个非常重要的属性Runnable target(线程执行目标)为空,线程创建后执行run方式的时候就无法执行,直接结束

	public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

target为空,什么也不执行

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

2、创建Thread线程

class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<5;i++){
            System.out.println(currentThread().getName()+"&"+i);
        }
    }
}
public class DemoThread {
    public static void main(String[] args) {
        Thread thread=new MyThread();
        thread.start();
    }
}

因为是继承之类的实例,所以start启动线程后实现的是继承类的run方法

二、实现Runnable

1、代码实现

实现Runnable接口,重新run方法,编写线程逻辑

class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i=1;i<=5;i++){
            System.out.println(Thread.currentThread().getName()+"&"+i);
        }
    }
}
public class DemoRunnable {
    public static void main(String[] args) {
        Thread thread=new Thread(new MyRunnable(),"runnableThread");
        thread.start();
    }
}

非热点代码可通过匿名类和lambda的方法来替代实现类

2、实现原理

	public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
     @Override
    public void run() {
    
        if (target != null) {
            target.run();
        }
    }

通过传入Runnable类型的target构造Thread实例
调用start方法启动线程后,执行run方法,run方法判断target不为空,执行target的run方法,就是实现Runnable的run方法

3、继承Thread和实现Runnable的优缺点

  • 实现Runnable的缺点:
    1、因为当前类不是线程类,而是线程的target执行目标类,所以只能作为参数进行构造才能创建真正的线程
    2、不是Thread。所以无法直接调用Thread的方法,只能使用Thread.staticfun()的方式调用Thread中的静态方法
  • 实现Runnable的优点:
    1、避免了单继承的局限性,如果一个类已经继承了,需要有多线程的场景,extend Thread显然是不行的
    2、逻辑和数据更好的分离,实现Runnable适合资源共享的场景,因为多个new Thread可以传入同一个target,,可以共享计算和资源

三、实现Callable交给FutureTask

因为业务带来的返回线程结果的问题,Thread和Runnable显然是不行了,于是在java1.5的时候出现了Callable接口,能够返回线程的结果

1、Callable

泛型接口,声明是什么类型,就通过call()获取什么类型的结果

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

但是Thread中显然没有能够传入Callable类型的构造器,所以需要借助另一个类FutureTask

2、FutureTask

public class FutureTask<V> implements RunnableFuture<V>

实现了RunnableFuture接口,进去看看

3、RunnableFuture

RunnableFuture继承了了Runnable,所以可以作为中间者作为target替代Callable传入Thread构造器

public interface RunnableFuture<V> extends Runnable, Future<V> {

    void run();
}

4、Future

Future的三大功能
(1)能够取消异步执行中的任务。
(2)判断异步任务是否执行完成。
(3)获取异步任务完成后的执行结果。

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);//取消异步任务的执行

    boolean isCancelled();//判断任务是否取消

    boolean isDone();//判断是否执行完成

    V get() throws InterruptedException, ExecutionException;//获取线程执行结果
    
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;//获取结果,同时设置等待的最长时间
}

get()方法是阻塞的,如果获取到的结果为空,就会阻塞等待,直到获取到结果
而get(long timeout, TimeUnit unit)方法在get()的基础上设置了等待时间,如果timeout时间后没有获取到就结束阻塞

5、原理分析

因为FutureTask继承了RunnableFuture,RunnableFuture继承了Runnable,所以FutureTask可以作为target构造Thread

public Thread(Runnable target) 
 public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

而且FutureTask构造器可以传入Callable来作为new Thread创建线程的中间者

再来看一下FutureTask获取结果的关键方法

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;//获取callable
            if (c != null && state == NEW) {//不为空,且为创建态
                V result;
                boolean ran;
                try {
                    result = c.call();//通过callable的call方法获取线程结果
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);//set方法为outcome结果赋值
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

通过callable对象的call方法获取结果值:
result=c.call();
没有异常就调用set方法为outcome线程结果赋值:
set(result);

	 protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

get获取线程结果就是调用私有方法report(int s)来获取outcome

	 public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
    private V report(int s) throws ExecutionException {
        Object x = outcome;//获取结果值
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }
   

6、代码实现

class MyCallable implements Callable<Integer>{
    private Integer money=1000;
    @Override
    public Integer call() throws Exception {
        for(int i=1;i<=5;i++){
            money-=100;
            System.out.println(Thread.currentThread().getName()+i+":取走了"+100);
        }
        return money;
    }
}

public class DemoCallable {
    public static void main(String[] args) {
        MyCallable myCallable=new MyCallable();
        FutureTask<Integer> futureTask=new FutureTask<>(myCallable);

        new Thread(futureTask, "callThread").start();
        try {
            System.out.println("剩余:"+futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

7、分析结果

通过Callable类对象构造FutureTask
FutureTask作为target中间者构造Thread
start方法启动线程后执行run(),target不为空执行FutureTask对象的run方法
run方法通过调用传入的callable对象的call()方法获取结果,并通过set()方法赋值给outcome
FutureTask对象可以通过get()方法来调用report获取outcome结果值

四、线程池

以上的线程创建,执行完后就自动销毁了,不可复用,而系统创建一个线程是十分消耗资源的,在高并发场景中,肯定不能频繁的创建和销毁线程,而是要对已经提供好的线程进行复用,就是线程池技术

在这里不对不同线程池以及八大参数进行深入,只了解是怎么创建线程的

1、原理分析

Java提供了一个静态工厂来创建不同的线程池,Executors工厂类
有不同的静态工厂方法可以构建不同的线程池,例如:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

可以创建一个线程数量为nThreads的ExecutorService类型的的线程池

每次执行异步任务的时候,可以通过ExecutorService去对任务进行提交或者执行
ExecutorService提交异步执行target目标任务的常用方法有:

//执行Runnable类型的目标实例
void execute(Runnable command);

//提交Runnable类型的目标实例,并返回Future对象,可通过该对象对任务进行控制
Future<?> submit(Runnable task);

//提交Callable类型的目标实例,并返回Future对象,可通过该对象对任务进行控制
<T> Future<T> submit(Callable<T> task);

2、代码实现

public class DemoPool{
    //通过静态工厂获取线程数量为3的线程池,
    private static ExecutorService pool= Executors.newFixedThreadPool(3);

    public static void main(String[] args) throws Exception{
        pool.execute(new MyRun());
        Future<Integer> future = pool.submit(new MyCall());
        Integer res=future.get();
        System.out.println(res);
    }
}

以上是关于JUC-线程创建的四种方式+原理分析的主要内容,如果未能解决你的问题,请参考以下文章

Java线程的四种创建方式

JUC并发编程之CompletableFuture基础用法

JUC并发编程之CompletableFuture基础用法

JUC并发编程之CompletableFuture基础用法

创建多线程的四种方式

浅析java中的四种线程池