当前位置: 首页 > 图灵资讯 > 技术篇> Java并发基础(一)

Java并发基础(一)

来源:图灵教育
时间:2023-06-15 09:38:30

创建线程有四种方法

JVM是操作系统的一个过程,在一个过程下可以有多个线程。在Java中,只有Thread类可以通过一个被Native关键字(当Java调用非Java代码编写的接口时,用这个关键字进行修改)来修改名为Start0()的向当地操作系统申请线程的资源。

一、继承Thread类

Threadstart0()方法只能用于在java中申请操作系统的线程资源。

1.新建类继承Thread类,重写run方法,run方法是开启线程的主要业务。

public class ThreadTest extends Thread {@Overridepublic void run() {//todo Systemm需要执行的业务.out.println("my thread test");}}Java并发基础(一)_线程池

在Thread源代码中,明确指出Thread的子类需要继承该方法。如果该线程由Runnable对象构建,则调用Runnnable实现类中的Run()方法。

/**     * If this thread was constructed using a separate     * <code>Runnable</code> run object, then that     * <code>Runnable</code> object's <code>run</code> method is called;     * otherwise, this method does nothing and returns.     * <p>     * Subclasses of <code>Thread</code> should override this method.     *     * @see     #start()     * @see     #stop()     * @see     #Thread(ThreadGroup, Runnable, String)     */    @Override    public void run() {        if (target != null) {            target.run();        }    }Java并发基础(一)_ide_02

2.start()方法在main方法中打开线程

public class TestMain {public static void main(String[] args) {ThreadTest threadTest = new ThreadTest();        //ThreadTestat方法调用start方法.start();}}

为什么不调用run()方法来调用start()方法呢?

在Threarun()方法的源代码中,我们可以看到run方法中没有打开线程,只是一种普通的方法。调用run()方法不会在当前线程中启动新的run()方法,而是在threadstart()方法中真正实现多线程。

public synchronized void start() {        ///判断线程转态是否已启动,多次启动会报错        if (threadStatus != 0)            throw new IllegalThreadStateException();        ///将线程添加到线程组中        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 */            }        }    }

在start()的源代码中,可以看到线程实际上是通过start0()启动的。该方法将运行新的线程,并调用run()方法。

重复调用start()方法意味着当线程多次启动时,它会被抛出 IllegalthreadStatexception异常。

二、实现Runnable接口

实现Runnnable接口的新线程类,重写Run方法。

public class RunnableTest implements Runnable{@Overridepublic void run() {System.out.println("my runnable test");}}

此时此类无法构建新的线程。还需要将其传输到Thread类,并通过Thread的satart()方法调用。

public class TestMain {public static void main(String[] args) {RunnableTest runnableTest = new RunnableTest();Thread thread = new Thread(runnableTest);thread.start();}}

为什么要把runnabletest传入Thread和Thread?.start()能调用runnnnabletest的run()方法?

这就需要看Thread的构造函数了。

private void init(ThreadGroup g, Runnable target, String name,                      long stackSize, AccessControlContext acc,                      boolean inheritThreadLocals) {        //...        this.target = target;        //...    }

init()法在构造函数中被调用,在这种方法中可以看到输入的runnable被赋值给this.target,在我们之前看到的run()方法的源代码中,当target不在空时执行时,target.run(),所以最后在调用run方法时,调用我们传入runnable的run()方法。

三、实现Callable接口

Callable可以说是Runnnable最大的特别不同之处在于,callable有返回值,他与future同用,callable产生结果,而future则用于获取结果。futureget()用于等待Calable结束并获得其执行结果,这种方法会造成阻塞。

@FunctionalInterfacepublic interface Callable<V> {    /**     * Computes a result, or throws an exception if unable to do so.     *     * @return computed result     * @throws Exception if unable to compute a result     */    V call() throws Exception;}

从Callabe的源代码可以看出,它是一个函数接口,只有一个call()方法,call()方法中要调用的代码与run()方法中的代码相同。

我们首先设置一个类来实现Callable接口,此时定义的返回类型是String

public class CallableTest implements Callable<String> {@Overridepublic String call() throws Exception {return "my callable test";}}

在main方法中,我们需要将callable的实现类传输到futuretask进行包装,然后将futuretask传输到theard启动线程执行。

public class TestMain {public static void main(String[] args) throws ExecutionException, InterruptedException {CallableTest callableTest = new CallableTest();FutureTask<String> futureTask = new FutureTask<>(callableTest);Thread thread = new Thread(futureTask);thread.start();System.out.println(futureTask.get());}}

首先,让我们来看看FunnableFuture接口,它实现了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();}Java并发基础(一)_创建线程_11

可以看出,futuretask中有一种实现run()的具体方法,而thead则是如此.start()调用futuretask中的run()方法。让我们来看看futuretask的结构函数。在结构函数中,calable被赋值为类本身的私有属性

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

在futuretaskrun()方法中

public void run() {        if (state != NEW ||            !UNSAFE.compareAndSwapObject(this, runnerOffset,                                         null, Thread.currentThread()))            return;        try {            Callable<V> c = callable;            if (c != null && state == NEW) {                V result;                boolean ran;                try {                    //等待call()方法返回结果                    result = c.call();                    ran = true;                } catch (Throwable ex) {                    result = null;                    ran = false;                    setException(ex);                }                if (ran)                    set(result);            }        } 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);        }    }

可以看到 result = c.call();,c是刚刚传入Futuretask的callable,实际上是调用callable实现类的call方法进行返回。

/**     * Sets the result of this future to the given value unless     * this future has already been set or has been cancelled.     *     * <p>This method is invoked internally by the {@link #run} method     * upon successful completion of the computation.     *     * @param v the value     */    protected void set(V v) {        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {            outcome = v;            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state            finishCompletion();        }    }

最后将result结果传入set方法,赋值给privatetet Object outcome,当我们调用get方法时,会输出这个变量的值。

四、线程池创建线程

使用线程池创建可以很好地重用线程。我们使用Thread创建一个,它将在线程执行后被销毁。线程的创建和销毁是一个非常耗尽系统资源的问题。我们可以把线程池看作是一个制造和存储线程的工厂。当线程池有线程时,直接使用它,而不是新的,然后在使用后返回到线程池。有许多配置参数需要自己配置,这里我们使用newfixedThreadPol,可以创建固定尺寸的线程池。

public class TestMain {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService executorService = Executors.newFixedThreadPool(2);Future<?> future = executorService.submit(new RunnableTest());Future<?> future1 = executorService.submit(new CallableTest());executorService.execute(new RunnableTest());System.out.println(future1.get());}}

在这里,我们创建了一个包含两个线程的线程池,分别调用submit()和execute()方法。我们可以看到两者之间的区别

/**     * Submits a Runnable task for execution and returns a Future     * representing that task. The Future's {@code get} method will     * return {@code null} upon <em>successful</em> completion.     *     * @param task the task to submit     * @return a Future representing pending completion of the task     * @throws RejectedExecutionException if the task cannot be     *         scheduled for execution     * @throws NullPointerException if the task is null     */    Future<?> submit(Runnable task);

submit()方法属于ExecutorService,并且有返回值,可以输入Runnable和callable

/**     * Executes the given command at some time in the future.  The command     * may execute in a new thread, in a pooled thread, or in the calling     * thread, at the discretion of the {@code Executor} implementation.     *     * @param command the runnable task     * @throws RejectedExecutionException if this task cannot be     * accepted for execution     * @throws NullPointerException if command is null     */    void execute(Runnable command);

Execute是Executor的方法,只能传入Runnable,没有返回值。

小结

1.线程总是调用Threadrun()方法。

2.使用Runnable创建线程只需要实现接口,而继承Thread类后,其他类别(java单继承)不能继承,使用Runnable创建线程可以更灵活。

3.根据使用情况,使用Callable可以获得线程的返回值。

4.以上四种方法都可以创建线程。他们的最终底层是Thread类,通过Thread类Start0()申请线程资源