juc-01-创建线程

这一系列文章开始讲解java多线程相关知识,下面这篇文章简单地介绍下新建线程的3种方式。

1 浅谈进程和线程

1.1 进程

进程是正在运行的程序的实例,是系统进行资源分配的基本单位

进程的3个基本状态

就绪(Ready)

当进程已分配到除CPU以外的所有必要的资源,只要获得CPU便可立即执行。

运行(Running)

当进程已获得CPU,其程序正在CPU上执行。

阻塞(Blocked)

正在执行的进程,由于等待某个事件发生而无法执行时,便放弃CPU而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。

1.2 线程

线程是操作系统能够进行任务调度和执行的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

线程5种状态

新建(new Thread)

当创建Thread类( new Thread() )的一个实例时,在调用start方法之前的处于新建状态,它还没有分配到系统资源,这时只能启动或终止它,任何其他操作都会引发异常。

就绪(runnable)

执行 thread.start() 方法后,操作系统分配该线程除CPU之外所必须的资源,等待CPU调度线程即可执行。

运行(running)

线程获得CPU资源(获得CPU调度)执行任务。

阻塞(Blocked)

如果一个线程在执行过程中需要等待某个事件发生,则让出CPU暂停运行,即进入堵塞状态。

死亡(dead)

当run() 方法返回,或别的线程调用stop()方法,线程进入死亡态。

线程状态转换图如下:

1.3 进程和线程的关系

2 Thread类创建API

方法描述
Thread()构造器,线程名称:"Thread-"+线程序号
Thread(String name)带name的构造器,name: 新线程的名称
Thread(Runnable target)带Runnable的构造器,
Runnable: 线程被启动后被CPU调度时,会执行Runnable实例的run()方法
Thread(Runnable target, String name)带Runnable和name的构造器,
Runnable: 线程被启动后被CPU调度时,会执行Runnable实例的run()方法,
name: 新线程的名称
synchronized void start()启动此线程,JVM将会执行这个线程的 run() 方法

3 创建线程方式一:extends Thread

定义一个类 extends Thread,并重写 run() 方法,run()方法的方法体就子线程的任务逻辑.。

/**
 * Description: 自定义线程类, extends Thread
 *
 * @author Xander
 * datetime: 2020/9/16 19:22
 */
public class XdThread extends Thread {

    public XdThread() {
    }

    /**
     * 指定线程名称
     * @param name 线程名称
     */
    public XdThread(String name) {
        super(name);
    }

    /**
     * run()方法是子线程执行的任务逻辑
     */
    @Override
    public void run() {
        System.out.println("子线程名称:" + Thread.currentThread().getName() + " 开始执行任务...");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子线程名称:" + Thread.currentThread().getName() + " 任务结束...");
    }
}

    /**
     * 创建线程方式一:定义一个类 extends Thread,并重写 run() 方法,run()方法的方法体就子线程的任务逻辑
     */
    @Test
    public void testNewThread() throws InterruptedException {
        //新建线程,不指定线程名称,系统会给线程定义一个名称
        XdThread thread = new XdThread();
        //启动子线程
        thread.start();

        //新建线程,指定线程名称
        XdThread threadWithName = new XdThread("Another-Thread");
        //启动子线程
        threadWithName.start();
        TimeUnit.SECONDS.sleep(3);
    }


运行结果:

子线程名称:Thread-0 开始执行任务...
子线程名称:Another-Thread 开始执行任务...
子线程名称:Thread-0 任务结束...
子线程名称:Another-Thread 任务结束...

4 创建线程方式二:Runnable

Runnable源码
Runnable 可以用 @FunctionalInterface 标识,是一个函数式接口,我们可以通过 lambda 表达式创建一个 Runnable 的实例。

@FunctionalInterface
public interface Runnable {
    void run();
}
/**
 * Description: 通过实现 Runnable 接口创建线程
 *
 * @author Xander
 * datetime: 2020/9/16 19:34
 */
public class XdRunnable implements Runnable {

    /**
     * 线程任务执行逻辑
     */
    @Override
    public void run() {
        System.out.println("Runnable方式,子线程名称:" + Thread.currentThread().getName() + " 开始执行任务...");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Runnable方式,子线程名称:" + Thread.currentThread().getName() + " 任务结束...");
    }
}

    /**
     * 创建线程方式二:实现Runnable接口
     */
    @Test
    public void testRunnable() throws InterruptedException {
        Thread thread = new Thread(new XdRunnable());
        //启动子线程
        thread.start();
        // 通过 Lambda 创建Runnable实例
        new Thread(() -> {
            System.out.println("我是另一个子线程,开始执行任务...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是另一个子线程,执行结束...");
        }).start();
        TimeUnit.SECONDS.sleep(3);
    }

运行结果:

Runnable方式,子线程名称:Thread-0 开始执行任务...
我是另一个子线程,开始执行任务...
我是另一个子线程,执行结束...
Runnable方式,子线程名称:Thread-0 任务结束...

5 创建线程方式三:Callable + FutureTask

上面两种方式创建线程是没有返回值,也不能抛出 checked Exception
如果我们的业务场景需要接收返回值,或者当子线程执行异常时,我们需要catch子线程抛出的异常,这时,我们就可以用到 Callable + FutrueTask 新建线程。

5.1 Runnable、Future、RunnableFuture、FutureTask 和 Callable 之间的关系

从下面的类图中,我们可以看出,RunnableFuture 接口继承了 Runnable 接口和 Future 接口,而 FutureTask 类实现了 RunnableFuture 接口,同时 Callable 是FutureTask类的一个成员变量。

5.2 Runnable、Future、RunnableFuture、FutureTask 和 Callable 的源码

5.2.1 Callable

定义 call() 方法计算结果,或在无法正常计算时抛出异常。

泛型V指定返回结果值的类型。

@FunctionalInterface
public interface Callable<V> {
    /**
     * 计算结果,或在无法正常计算时抛出异常。
     */
    V call() throws Exception;
}

5.2.2 Runnable

当线程被cpu执行时,会执行run()方法。

@FunctionalInterface
public interface Runnable {
    void run();
}

5.2.3 Future

Future定义了几个操作方法,用于操作 Callable 的 call() 方法的执行任务。

public interface Future<V> {

    /**
     * 取消任务执行
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * 判断任务是否是在完成之前被取消了
     */
    boolean isCancelled();

    /**
     * 如果任务执行结束,返回true
     */
    boolean isDone();

    /**
     * 等待任务完成,获取结果
     */
    V get() throws InterruptedException, ExecutionException;

    /**
     * 在规定时间内完成了任务,那么就会正常获取到返回值;而如在指定时间内没有计算出结果,那么就会抛出 TimeoutException
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

5.2.4 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();
}

5.2.5 FutureTask

实现 RunnableFuture,因此FutureTask也是Runnable和Future接口的实现类,也就是实现了 Runnable 和 Future 接口中的抽象方法。
也就是说 FutureTask 可以作为 Runnable ,通过Thread(Runnable target)或者Thread(Runnable target, String name)方式新建线程,当线程被CPU执行时,会调用 FutureTask 的 run()方法。从下面源码中,我们看到run()方法中会执行 Callable 实例的 call() 方法。

public class FutureTask<V> implements RunnableFuture<V> {
...
    //成员变量Callable
    private Callable<V> callable;
...
    // 构造器,入参是一个 Callable 实例
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
...
    // FutureTask也是一个Runnable接口的实现类,线程被CPU执行时,会调用run()方法,run()方法中会执行 Callable 实例的 call() 方法。
    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 {
                    // 执行 Callable 实例的 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);
        }
    }
...
}

5.2.6 小结

我们可以用 Future.get() 来获取 Callable 接口返回的执行结果,还可以通过 Future.isCancelled() 判断任务是否被取消了等。
在call() 未执行完毕之前,调用get()的线程(假定此时是主线程)会被阻塞,直到 call() 方法返回了结果后,此时future.get() 才会得到该结果,然后主线程才会切换到 runnable(就绪) 状态,申请cpu调度。
所以 Future 是一个 存储器,它存储了 call() 这个任务的结果,而这个任务的执行时间是无法提前确定的,因为这完全取决于 call() 方法执行的情况。

Future接口API

方法描述
boolean cancel(boolean mayInterruptIfRunning)取消任务执行
boolean isCancelled()判断任务是否是在完成之前被取消了
boolean isDone()如果任务执行结束,返回true
V get() throws InterruptedException, ExecutionException等待任务完成,获取结果
- 1. 任务正常完成:get方法会立刻返回结果
- 2. 任务尚未完成(任务还没开始或者进行中):get将阻塞并直到任务完成。
- 3. 任务执行过程中抛出 Exception:get 方法会抛出 ExecutionException:这里的抛出异常,是Call()执行时产生的那个异常,看到这个异常类型是 java.util.concurrent.ExecutionException。不论 call() 执行时抛出的异常类型是什么,最后get方法抛出的异常类型都是 ExecutionException。
- 4. 任务被取消:get方法会抛出 CancellationException
- 5. 任务超时,get方法有一个重载方法,是传入一个延迟时间的,如果时间到了还没有获得结果,get方法就会抛出TimeoutException。
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;在规定时间内完成了任务,那么就会正常获取到返回值;而如在指定时间内没有计算出结果,那么就会抛出 TimeoutException,超时不获取,任务需取消

5.3 Callable + FutureTask 创建线程,并获取结果

因为 FutureTask是 Runnable 和 Future 接口的实现类。

/**
 * Description: 通过实现 Callable 接口创建线程,允许有返回值
 *
 * @author Xander
 * datetime: 2020/9/16 19:52
 */
public class XdCallable implements Callable<Integer> {

    /**
     * 线程任务执行逻辑
     *
     * @return
     * @throws Exception
     */
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable方式,子线程名称:" + Thread.currentThread().getName() + " 开始执行任务...");
        // 返回一个 0-100(不包含) 随机整数
        Random random = new Random();
        int num = random.nextInt(100);
        TimeUnit.SECONDS.sleep(2);
        System.out.println("Callable方式,子线程名称:" + Thread.currentThread().getName() + " 任务结束...");
        return num;
    }
}


    /**
     * 创建线程方式三:实现 Callable 接口
     */
    @Test
    public void testCallable() throws ExecutionException, InterruptedException {
        // 通过子线程返回一个 0-100(不包含) 随机整数
        FutureTask future = new FutureTask(new XdCallable());
        // FutureTask 可以作为 Runnable ,通过 Thread(Runnable target) 新建线程
        Thread thread = new Thread(future);
        //启动子线程
        thread.start();
        System.out.println("子线程返回:" + future.get());
        System.out.println("主线程结束");
    }

运行结果:

Callable方式,子线程名称:Thread-0 开始执行任务...
Callable方式,子线程名称:Thread-0 任务结束...
子线程返回:40
主线程结束

6 结语

  • 方式一:extends Thread,实现简单,但是由于类单一继承原则,这种方式下线程类已经继承Thread类了,就不能再继承其他类;
  • 方式二:实现 Runnable 接口方式,不能返回一个返回值,也不能抛出 checked Exception。
  • 方式三:实现 callable 接口,允许子线程有返回值,能够抛出 checked Exception。

extends Thread 方式限制了类扩展,所以在工作中用得较少;
如果子线程没有返回值,也不需要在调用线程中捕获子线程抛出的异常,则可以使用 Runnable 方式新建线程;
如果需要子线程有返回值或者能够抛出 checked Exception,则可以使用 Callable + FutureTask 方式新建线程。

代码:
https://github.com/wengxingxia/002juc.git

[慕课手记同步:juc-01-创建线程] https://www.imooc.com/article/310748

福利: 关注同步公众号:黑桃,回复“电子书”,领取Java技术书籍pdf(不断更新)。

 


欢迎关注公众号"黑桃"

### Java JUC AQS 并发编程 抽象队列同步器 使用教程 源码解析 #### 什么是AQS? `AbstractQueuedSynchronizer`(简称AQS),作为Java并发包中的核心组件之一,提供了用于实现锁和其他同步器的基础框架。它不仅简化了锁和同步工具的创建过程,还提高了这些工具的工作效率[^2]。 #### 类图结构与工作原理 AQS的设计围绕着一个FIFO(先进先出)等待队列展开,该队列由多个节点组成,每个节点代表了一个正在等待获取资源的线程。每当有新的竞争者未能立即获得所需资源时,就会被构造成一个新的节点并加入到这个队列之中;而当现有持有者释放其持有的资源之后,则会从队头开始依次唤醒后续等待者去尝试占有资源[^5]。 #### 同步模式分类 为了适应不同场景下的需求,AQS支持两种主要类型的同步方式——独占式以及共享式: - **独占式**:一次只允许单个线程访问临界区,在这种情况下其他任何试图进入同一区域内的请求都将被迫挂起直到前序操作完成为止; - **共享式**:允许多个读取者同时存在而不互相干扰,只要不存在写入动作发生即可保持一致性和安全性[^3]。 #### 自定义同步器的关键接口 对于想要利用AQS来构建特定行为逻辑的新类型而言,开发者通常需要重载以下几个抽象方法以适配具体的应用环境: - `tryAcquire(int arg)` 和 `tryRelease(int arg)` - `tryAcquireShared(int arg)` 及 `tryReleaseShared(int arg)` - `isHeldExclusively()` 上述函数分别对应于独占/共享模式下对资源的操作控制流程,通过合理地覆盖它们可以轻松打造出满足业务特性要求的各种高级别同步原语[^1]。 ```java public class CustomSync extends AbstractQueuedSynchronizer { protected boolean tryAcquire(int acquires) { // 实现具体的独占式获取逻辑 return super.tryAcquire(acquires); } protected boolean tryRelease(int releases) { // 实现具体的独占式释放逻辑 return super.tryRelease(releases); } } ``` #### 队列管理机制详解 在实际运行过程中,AQS内部维护了一条双向链表形式的数据结构用来存储各个待处理的任务单元。每当新成员到来之时便会调用`enqueue()`方法将其追加至末端位置上形成完整的链条关系网状链接,并且借助CAS指令保证整个插入过程的安全可靠性质不受外界因素影响破坏[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值