线程的基本概念以及线程的创建方式

  

    在接下来的一段时间内会学习多线程相关知识,文章的主要内容是参考<<Java多线程编程核心技术>>,作者高洪岩。

    所有代码中都用到了slf4j日志,主要代替System.out.print功能,而且所有的代码最后整理放在git,git地址:https://github.com/qiao-zhi/ThreadStudy

1.线程相关概念

1.进程与线程的概念

  进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

   实际上完全可以将运行在内存中的exe理解成进程,进程是受操作系统管理的最基本单元。

    

  那么什么是线程呢?线程可以理解成在进程中独立运行的子任务。比如:QQ.exe运行时就有很多的子任务在同时运行。比如我们可以同时传文件、听音乐、发送图片等功能,这些都有对应的线程在后台默默的运行。

  实际上,在Java的世界中,启动一个JVM可以理解为一个进程,进程里面可以创建好多线程,实现一些异步的功能。在JavaEE中比如一个大文件上传或者批量添加数据库等操作,非常的耗时,我们可以新开一个线程进行操作,然后提前将处理结果返回给用户,这样可以增加用户友好性。

2.多线程的优势

   如果线程使用得当,线程可以有效地降低程序的开发和维护成本,同时提升复杂应用程序的性能。线程能够将大部分的异步工作流转换成串行工作流,因此更好地模拟人类的工作方式和交互方式。

2.1发挥多处理器的强大能力

  现在,多处理器系统正日益盛行,并且价格不断降低,即时在低端服务器和中断桌面系统中,通常也会采用多个处理器,这种趋势还在进一步加快,因为通过提高时钟频率来提升性能已变得越来越困难,处理器生产厂商都开始转而在单个芯片上放置多个处理器核。试想,如果只有单个线程,双核处理器系统上程序只能使用一半的CPU资源,拥有100个处理器的系统上将有99%的资源无法使用。多线程程序则可以同时在多个处理器上执行,如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率。

 2.2建模的简单性

  通过使用线程,可以将复杂并且异步的工作流进一步分解为一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置进行交互。我们可以通过一些现有框架来实现上述目标,例如Servlet和RMI,框架负责解决一些细节问题,例如请求管理、线程创建、负载平衡,并在正确的时候将请求分发给正确的应用程序组件。编写Servlet的开发人员不需要了解多少请求在同一时刻要被处理,也不需要了解套接字的输入流或输出流是否被阻塞,当调用Servlet的service方法来响应Web请求时,可以以同步的方式来处理这个请求,就好像它是一个单线程程序。

 2.3异步事件的简化处理

  服务器应用程序在接受多个来自远程客户端的套接字连接请求时,如果为每个连接都分配其各自的线程并且使用同步I/O,那么就会降低这类程序的开发难度。如果某个应用程序对套接字执行读操作而此时还没有数据到来,那么这个读操作将一直阻塞,直到有数据到达。在单线程应用程序中,这不仅意味着在处理请求的过程中将停顿,而且还意味着在这个线程被阻塞期间,对所有请求的处理都将停顿。为了避免这个问题,单线程服务器应用程序必须使用非阻塞I/O,但是这种I/O的复杂性要远远高于同步I/O,并且很容易出错。然而,如果每个请求都拥有自己的处理线程,那么在处理某个请求时发生的阻塞将不会影响其他请求的处理。

 2.4响应更灵敏的用户界面

  传统的GUI程序都是单线程的,但是在现代的GUI程序中,例如AWT和SWing等工具,都采用一个事件分发线程来代替主循环事件。如果在线程中执行的任务都是短暂的,那么界面响应的灵敏度就越高,因为事件处理线程能够很快的处理用户的动作。

2.3多线程的风险

2.3.1安全性问题 

  线程安全性是非常复杂的,在没有充足同步的情况下,线程执行的顺序是不课预测的,甚至会产生很多奇怪的结果。

  由于多个线程要共享相同的内存空间,并且是并发运行,因此它们可能会访问或者修改修改其他线程正在使用的变量。

  方法内部的变量是不会发生线程安全的,只有成员变量才会发生。因为方法内部的线程不共享,如果对JVM有所了解的应该明白Java中堆和永久代是线程共享的,虚拟机栈和本地方法栈、PC(程序计数器)是线程私有的。

  非线程安全主要是指多个对象对同一个对象中的同一个对象实例进行操作时会出现值被修改、值不同步的情况,进而影响程序的执行流程。

  如果多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误。有三种方式可以解决这个问题。

(1)不在线程间共享该状态变量

(2)将状态变量修改为不可变的变量

(3)在访问变量时使用同步(这里说所的同步包括synchronized锁、volitate型变量、显示锁以及原子变量)

一个典型的线程非安全的例子:

package cn.qlq.thread.one;

public class ThreadSafe extends Thread {

    private int i;// 默认值为0

    @Override
    public void run() {
        System.out.println("i=" + (i++) + ",threadName=" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ThreadSafe run = new ThreadSafe();
        // 开启十个线程进行计算
        for (int i = 0; i < 10; i++) {
            Thread t1 = new Thread(run);
            t1.start();
        }
    }
}

结果:(i++实际上分为三步:读取i的值,将值加1,然后将计算结果写入i。这是一个"读取-修改-写入"的操作,并且其结果状态依赖于之前的状态。)

  虽然System.out.print()方法加了synchronized,但是i++是在print()方法前面执行的,也就是相当于多个方法的调用,先执行的i++,在将结果传入print进行打印功能。

i=0,threadName=Thread-2
i=1,threadName=Thread-3
i=0,threadName=Thread-1
i=2,threadName=Thread-5
i=3,threadName=Thread-6
i=4,threadName=Thread-4
i=5,threadName=Thread-7
i=6,threadName=Thread-8
i=7,threadName=Thread-10
i=8,threadName=Thread-9

  上面代码也说明了线程的执行顺序的随机性,并不是先start的就一定会先执行。

解决办法:

(1)run方法加synchronized关键字

    @Override
    public synchronized void run() {
        System.out.println("i=" + (i++) + ",threadName=" + Thread.currentThread().getName());
    }

(2)使用并发包的原子变量

package cn.qlq.thread.one;

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadSafe extends Thread {

    private AtomicInteger i = new AtomicInteger(0);// 默认值为0

    @Override
    public void run() {
        System.out.println("i=" + (i.getAndIncrement()) + ",threadName=" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ThreadSafe run = new ThreadSafe();
        // 开启十个线程进行计算
        for (int i = 0; i < 10; i++) {
            Thread t1 = new Thread(run);
            t1.start();
        }
    }
}

2.3.2 活跃性问题

  常见的活跃性问题例如死锁、饥饿、活锁等问题。

2.3.3性能问题

  在多线程程序中,当线程调度器临时挂起活跃线程并转而运行另一个线程时,就会频繁的切换上下文,这种操作会带来极大的开销:保存和恢复执行上下文,丢失局部性,并且CPU时间将更多地花在线程调度而不是线程运行商。

2.线程的创建

  一个进程在运行时至少会有一个线程在后台运行,这种情况在Java中也是存在的。这些线程在后台默默的运行,比如调用public static void main()方法的线程就是这样的,而且它是由JVM创建的。

package cn.qlq.thread.one;

public class Test1 {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}

结果:

main

  控制台输出的main实际上是一个叫做main的线程在执行main方法的代码。另外,控制台输出的main和main方法没有任何关系,一个是线程的名称叫做main,一个是方法的名字是main。

1.继承Thread类

  实现多线程编程的方式主要有两种:一种是继承Thread类,一种是实现Runnable接口。(实际还有第三种方式Callable接口)

查看Thread类的结构:

public class Thread implements Runnable

  可以发现Thread类实现了Runnable接口,它们具有多态关系。

  其实使用继承Thread类的方式创建线程的时候,最大的局限就是不支持多继承,Java语言本身的语言特性就是单继承。所以为了支持多继承,完全可以采用实现Runnable接口的方式,一边实现一边继承。实际这两种方式工作时的性质是一样的,没有本质的区别。

实现方式:

package cn.qlq.thread.one;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SubThread extends Thread {

    private static final Logger log = LoggerFactory.getLogger(SubThread.class);

    @Override
    public void run() {
        log.debug("subThread run,threadname->{}", Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        SubThread subThread = new SubThread();
        subThread.start();
        log.debug("运行结束,threadname->{}", Thread.currentThread().getName());
    }
}

结果:

2018-12-04 22:16:05 [cn.qlq.thread.one.SubThread]-[DEBUG] 运行结束,threadname->main
2018-12-04 22:16:05 [cn.qlq.thread.one.SubThread]-[DEBUG] subThread run,threadname->Thread-0

上面结果表明,在使用多线程技术时,代码的运行结果与代码执行的顺序或调用顺序是无关的。

  线程是一个子任务,CPU以不确定的方式运行,或者说是以随机的时间来调用线程中的run方法。

  CPU执行哪个线程的run方法也具有不确定,不是先start的就一定会先执行run方法。start方法里面调用了一个native方法,仅仅是通知线程规划器此线程已经准备就绪,等待调用线程的run方法,具体执行哪个线程的run方法具有不确定性。

  直接调用thread.run方法就不是异步执行,而是同步,相当于一个普通的方法调用。(也常被作为面试题问run与start方法的区别)

2.实现Runnable接口

  如果想创建的线程已经有一个父类,就不能再继承Thread,所以就需要采用实现Runnable接口。

package cn.qlq.thread.one;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RunnableThread implements Runnable {

    private static final Logger log = LoggerFactory.getLogger(RunnableThread.class);

    @Override
    public void run() {
        log.debug("runnableThread run,threadname->{}", Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        RunnableThread runnableThread1 = new RunnableThread();
        Thread t1 = new Thread(runnableThread1);
        Thread t2 = new Thread(runnableThread1, "t2");
        t1.start();
        t2.start();
        log.debug("运行结束,threadname->{}", Thread.currentThread().getName());
    }
}

结果:

2018-12-04 22:17:02 [cn.qlq.thread.one.RunnableThread]-[DEBUG] 运行结束,threadname->main
2018-12-04 22:17:02 [cn.qlq.thread.one.RunnableThread]-[DEBUG] runnableThread run,threadname->Thread-0
2018-12-04 22:17:02 [cn.qlq.thread.one.RunnableThread]-[DEBUG] runnableThread run,threadname->t2

实现Runnable接口的方式的运行仍然离不开Thread类,查看Thread类的8个构造方法如下:(有两个接收runnable对象的方法)

   注意:Thread类也实现了Runnable接口,那也就意味着Thread(Runnable target)不仅可以传入Runnable接口的对象,还可以传入一个Thread类的对象,这样做完全可以将一个Thread类的run()方法交由其他的线程调用。

3.实现Callable接口

   多线程的实现方式有实现Runnable接口和继承Thread类(实际上Thread类也实现了Runnable接口),但是Runnable接口的方式有两个弊端,第一个是不能获取返回结果,第二个是不能抛出exception。但是Callable接口很好的解决了上面的问题。

1.不接收返回值,只是简单的开启线程执行一些任务

package cn.qlq.thread.one;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 实现callable接口,实现Callable接口
 * 
 *
 */
public class MyCallable implements Callable<String> {

    private static final Logger log = LoggerFactory.getLogger(MyCallable.class);

    /**
     * 实现call方法,接口中抛出异常。因为子类不可以比父类干更多的坏事,所以子类可以不抛出异常
     */
    @Override
    public String call() {
        log.debug(Thread.currentThread().getName() + "   执行callable的call方法");
        return "result";
    }

    public static void main(String[] args) {
        // 1.创建callable对象
        Callable<String> myCallable = new MyCallable();
        // 2.由上面的callable对象创建一个FutureTask对象
        FutureTask<String> oneTask = new FutureTask<String>(myCallable);
        // 3.由FutureTask创建一个Thread对象
        Thread t = new Thread(oneTask);
        // 4.开启线程
        t.start();
        log.debug("运行结束,threadname->{}", Thread.currentThread().getName());
    }

}

结果:

2018-12-04 22:26:21 [cn.qlq.thread.one.MyCallable]-[DEBUG] 运行结束,threadname->main
2018-12-04 22:26:21 [cn.qlq.thread.one.MyCallable]-[DEBUG] Thread-0 执行callable的call方法

2.使用ExecutorService、Callable、Future实现有返回结果的线程

1.开启单个线程接收返回值

package cn.qlq.thread.one;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 单个线程接收返回值
 * 
 * @author QiaoLiQiang
 * @time 2018年12月4日下午10:28:36
 */
public class MyCallable2 implements Callable<String> {

    /**
     * 实现call方法,接口中抛出异常。因为子类不可以比父类干更多的坏事,所以子类可以不抛出异常
     */
    @Override
    public String call() {
        System.out.println(Thread.currentThread().getName() + "   执行callable的call方法");
        return "result";
    }

    public static void main(String[] args) {
        test1();
    }

    /**
     * 单个线程
     */
    public static void test1() {
        // 1.创建固定大小的线程池
        ExecutorService es = Executors.newFixedThreadPool(1);
        // 2.提交线程任务,用Future接口接受返回的实现类
        Future<String> future = es.submit(new MyCallable());
        // 3.关闭线程池
        es.shutdown();
        // 4.调用future.get()获取callable执行完成的返回结果
        String result;
        try {
            result = future.get();
            System.out.println(Thread.currentThread().getName() + "	" + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

结果:

2018-12-04 22:30:01 [cn.qlq.thread.one.MyCallable]-[DEBUG] pool-1-thread-1 执行callable的call方法
main result

2.多个线程结合线程池接收返回值 

package cn.qlq.thread.one;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 多个线程多个返回值
 * 
 * @author QiaoLiQiang
 * @time 2018年12月4日下午10:30:58
 */
public class MyCallable3 implements Callable<String> {

    /**
     * 实现call方法,接口中抛出异常。因为子类不可以比父类干更多的坏事,所以子类可以不抛出异常
     */
    @Override
    public String call() {
        System.out.println(Thread.currentThread().getName() + "   执行callable的call方法");
        return "result";
    }

    public static void main(String[] args) {
        test2();
    }

    /**
     * 多个线程
     */
    public static void test2() {
        // 1.创建固定大小的线程池(5个)
        int threadNum = 5;
        ExecutorService es = Executors.newFixedThreadPool(threadNum);
        // 2.提交线程任务,用Future接口接受返回的实现类
        List<Future<String>> futures = new ArrayList<Future<String>>(threadNum);
        for (int i = 0; i < threadNum; i++) {
            Future<String> future = es.submit(new MyCallable());
            futures.add(future);
        }
        // 3.关闭线程池
        es.shutdown();
        // 4.调用future.get()获取callable执行完成的返回结果
        for (Future<String> future : futures) {
            try {
                String result = future.get();
                System.out.println(Thread.currentThread().getName() + "	" + result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

}

结果:

2018-12-04 22:31:46 [cn.qlq.thread.one.MyCallable]-[DEBUG] pool-1-thread-4 执行callable的call方法
2018-12-04 22:31:46 [cn.qlq.thread.one.MyCallable]-[DEBUG] pool-1-thread-2 执行callable的call方法
2018-12-04 22:31:46 [cn.qlq.thread.one.MyCallable]-[DEBUG] pool-1-thread-3 执行callable的call方法
2018-12-04 22:31:46 [cn.qlq.thread.one.MyCallable]-[DEBUG] pool-1-thread-5 执行callable的call方法
2018-12-04 22:31:46 [cn.qlq.thread.one.MyCallable]-[DEBUG] pool-1-thread-1 执行callable的call方法
main result
main result
main result
main result
main result

  关于Callable接口的详细使用方法参考另一篇:https://www.cnblogs.com/qlqwjy/p/9657981.html

  关于线程池的使用参考:https://www.cnblogs.com/qlqwjy/p/9470414.html

3.线程的状态

虚拟机中的线程状态有六种,定义在Thread.State中,查看源码如下:

 /**
     * A thread state.  A thread can be in one of the following states:
     * <ul>
     * <li>{@link #NEW}<br>
     *     A thread that has not yet started is in this state.
     *     </li>
     * <li>{@link #RUNNABLE}<br>
     *     A thread executing in the Java virtual machine is in this state.
     *     </li>
     * <li>{@link #BLOCKED}<br>
     *     A thread that is blocked waiting for a monitor lock
     *     is in this state.
     *     </li>
     * <li>{@link #WAITING}<br>
     *     A thread that is waiting indefinitely for another thread to
     *     perform a particular action is in this state.
     *     </li>
     * <li>{@link #TIMED_WAITING}<br>
     *     A thread that is waiting for another thread to perform an action
     *     for up to a specified waiting time is in this state.
     *     </li>
     * <li>{@link #TERMINATED}<br>
     *     A thread that has exited is in this state.
     *     </li>
     * </ul>
     *
     * <p>
     * A thread can be in only one state at a given point in time.
     * These states are virtual machine states which do not reflect
     * any operating system thread states.
     *
     * @since   1.5
     * @see #getState
     */
    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

1、新建状态NEW

  new了线程但是没有开始执行,比如: Thread t1 = new Thread();t1就是一个新建状态的线程。

2、可运行状态RUNNABLE

  new出来线程,调用start()方法即处于RUNNABLE状态了。处于RUNNABLE状态的线程可能正在Java虚拟机中运行,也可能正在等待处理器的资源,因为一个线程必须获得CPU的资源后,才可以运行其run()方法中的内容,否则排队等待

3、阻塞BLOCKED

  如果某一线程正在等待监视器锁,以便进入一个同步的块/方法,那么这个线程的状态就是阻塞BLOCKED

4、等待WAITING

  某一线程因为调用不带超时的Object的wait()方法、不带超时的Thread的join()方法、LockSupport的park()方法,就会处于等待WAITING状态

5、超时等待TIMED_WAITING

  某一线程因为调用带有指定正等待时间的Object的wait()方法、Thread的join()方法、Thread的sleep()方法、LockSupport的parkNanos()方法、LockSupport的parkUntil()方法,就会处于超时等待TIMED_WAITING状态

6、终止状态TERMINATED

  线程调用终止或者run()方法执行结束后,线程即处于终止状态。处于终止状态的线程不具备继续运行的能力。

 转换图如下:

代码测试线程的几种状态:

package cn.qlq.thread.one;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ThreadState extends Thread {

    private static final Logger log = LoggerFactory.getLogger(ThreadState.class);

    @Override
    public void run() {// 休眠5秒钟线程进入超时等待状态
        try {
            Thread.sleep(5 * 1000);
            synchronized (log) {// 模拟占用锁十秒钟
                Thread.sleep(10 * 1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 这个线程只是为了占用锁,所以先启动之后线程休眠2s启动第二个线程
        ThreadState threadState_0 = new ThreadState();
        threadState_0.start();
        Thread.sleep(2 * 1000);

        ThreadState threadState = new ThreadState();
        log.debug("state1->{}", threadState.getState());// 新建状态
        threadState.start();
        log.debug("state2->{}", threadState.getState());// 此时应当处于就绪状态
        Thread.sleep(1 * 1000);
        log.debug("state3->{}", threadState.getState());// 主线程休眠了3秒钟,threadState应该在休眠5秒钟,处于超时等待状态
        Thread.sleep(7 * 1000);
        log.debug("state4->{}", threadState.getState());// 主线程休眠了10秒钟,threadState应该在等待同步锁
        Thread.sleep(20 * 1000);
        log.debug("state5->{}", threadState.getState());// 主线程休眠了30秒钟,线程应该结束

    }
}

结果:

2018-12-05 22:16:04 [cn.qlq.thread.one.ThreadState]-[DEBUG] state1->NEW
2018-12-05 22:16:04 [cn.qlq.thread.one.ThreadState]-[DEBUG] state2->RUNNABLE
2018-12-05 22:16:05 [cn.qlq.thread.one.ThreadState]-[DEBUG] state3->TIMED_WAITING
2018-12-05 22:16:12 [cn.qlq.thread.one.ThreadState]-[DEBUG] state4->BLOCKED
2018-12-05 22:16:32 [cn.qlq.thread.one.ThreadState]-[DEBUG] state5->TERMINATED

补充:并行和并发的区别

并发是两个任务可以在重叠的时间段内启动,运行和完成。并行是任务在同一时间运行,例如,在多核处理器上。
并发是独立执行过程的组合,而并行是同时执行(可能相关的)计算。
并发是一次处理很多事情,并行是同时做很多事情。
应用程序可以是并发的,但不是并行的,这意味着它可以同时处理多个任务,但是没有两个任务在同一时刻执行。
应用程序可以是并行的,但不是并发的,这意味着它同时处理多核CPU中的任务的多个子任务。
一个应用程序可以即不是并行的,也不是并发的,这意味着它一次一个地处理所有任务。
应用程序可以即是并行的也是并发的,这意味着它同时在多核CPU中同时处理多个任务。

并行才是我们通常认为的那个同时做多件事情,而并发则是在线程这个模型下产生的概念。并发表示同时发生了多件事情,通过时间片切换,哪怕只有单一的核心,也可以实现“同时做多件事情”这个效果。根据底层是否有多处理器,并发与并行是可以等效的,这并不是两个互斥的概念。
原文地址:https://www.cnblogs.com/qlqwjy/p/10065881.html