• Java多线程1:进程与线程


    进程和线程

    讲线程和进程前,先讲下同步(Synchronous)、异步(Asynchronous)、并发(Concurrency)、并行(Parallelism)。

    同步(Synchronous)和异步(Asynchronous)

    同步和异步通常来形容一次方法调用,同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就

    可以继续后续的操。而异步方法通常会在另外一个线程中“真实”地执行。整个过程,不会阻碍调用者的工作。

    打个比方,比如购物,如果你去商场买空调,当你到了商场看重了一款空调,你就向售货员下单。售货员去仓库帮你调配物品。这天你热的是在不行了,就催着商家赶紧给你送货,于是你就在商店里面候着他们,

    直到商家把你和空调一起送回家,一次愉快的购物就结束了。这就是同步调用。

    不过,如果我们赶时髦,就坐在家里打开电脑,在电脑上订购了一台空调。当你完成网上支付的时候,对你来说购物过程已经结束了。虽然空调还没有送到家,但是你的任务已经完成了。商家接到你的订单后,

    就会加紧安平送货,当然这一切已经跟你无关了。你已经支付完成,想干什么就能去干什么,出去溜几圈都不成问题,等送货上门的时候,接到商家的电话,回家一趟签收就完事了。这就是异步调用。

    并发(Concurrency)和并行(Parallelism)

    并发和并行是两个非常容易被混淆的概念。他们都可以表示两个或者多个任务一起执行,但是侧重点有所不同。

    并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的,而并行是真正意义上的“同时执行”。

    大家排队在一个咖啡机上接咖啡,交替执行,是并发;两台咖啡机上面接咖啡,是并行。

    从严格意义上来说,并行的多任务是真的同时执行,而对于并发来说,这个过程只是交替的,一会执行任务A,一会执行任务B,系统会不停地在两者之间切换。但对于外部观察者来说,即使多个任务之间是串行并发的,也会造成多任务间并行执行的错觉。

    并发说的是在一个时间段内,多件事情在这个时间段内交替执行

    并行说的是多件事情在同一个时刻同事发生。

    实际上,如果系统内只有一个CPU,而使用多进程或者多线程任务,那么真实环境中这些任务不可能是真实并行的,毕竟一个CPU一次只能执行一条指令,在这种情况下多进程或者多线程就是并发的,而不是并行的(操作系统会不停地切换多任务)。真实的并行也只可能出现在拥有多个CPU的系统中(比如多核CPU)。

    我们再讲进程和线程的概念。

    进程

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

    进程具有的特征:

    • 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的

    • 并发性:任何进程都可以同其他进行一起并发执行

    • 独立性:进程是系统进行资源分配和调度的一个独立单位

    • 结构性:进程由程序,数据和进程控制块三部分组成

    360浏览器是一个进程、WPS也是一个进程,正在操作系统中运行的".exe"都可以理解为一个进程。

    我们双击.exe文件的时候,这个文件中的指令就会被系统加载,那么我们就能得到一个关于这个.exe程序的进程。进程是“活”的,或者说是正在被执行的。

    线程

    进程中独立运行的子任务就是一个线程。像QQ.exe运行的时候就有很多子任务在运行,比如聊天线程、好友视频线程、下载文件线程等等。

    一个程序至少有一个进程,一个进程至少有一个线程。

    使用多线程而不是多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程。

     多线程源码:https://github.com/xiaojiesir/Multithreading

    创建线程的方式

    创建线程有两种方式:

    1、继承Thread,重写父类的run()方法。

    public class MyThread00 extends Thread
    {        
        public void run()
        {
            for (int i = 0; i < 5; i++)
            {
                System.out.println(Thread.currentThread().getName() + "在运行!");
            }
        }
    }
    public static void main(String[] args)
    {
        MyThread00 mt0 = new MyThread00();
        mt0.start();
            
        for (int i = 0; i < 5; i++)
        {
            System.out.println(Thread.currentThread().getName() + "在运行!");
        }
    }

    看一下运行结果:

    main在运行!
    Thread-0在运行!
    main在运行!
    Thread-0在运行!
    main在运行!
    Thread-0在运行!
    main在运行!
    Thread-0在运行!
    Thread-0在运行!
    main在运行!

    看到main线程和Thread-0线程交替运行,效果十分明显。

    2、实现Runnable接口。和继承自Thread类差不多,不过实现Runnable后,还是要通过一个Thread来启动:

    public class MyThread01 implements Runnable
    {
        public void run()
        {
            for (int i = 0; i < 5; i++)
            {
                System.out.println(Thread.currentThread().getName() + "在运行!");
            }
        }
    }
    public static void main(String[] args)
    {
        MyThread01 mt0 = new MyThread01();
        Thread t = new Thread(mt0);
        t.start();
            
        for (int i = 0; i < 5; i++)
        {
            System.out.println(Thread.currentThread().getName() + "在运行!");
        }
    }

    效果也十分明显:

    main在运行!
    Thread-0在运行!
    main在运行!
    Thread-0在运行!
    main在运行!
    Thread-0在运行!
    main在运行!
    Thread-0在运行!
    main在运行!
    Thread-0在运行!

     3、实现Callable接口,实现call方法。这个比较复杂,先简单说明下,后续详细讲。有两种方法

     先定义一个类实现callable接口

     1 class ThreadDemo implements Callable<Integer> {
     2 
     3     @Override
     4     public Integer call() throws Exception {
     5         int sum = 0;
     6 
     7         for (int i = 0; i <= 100000; i++) {
     8             sum += i;
     9         }
    10 
    11         return sum;
    12     }
    13 
    14 }

    通过FutureTask包装器来创建Thread线程

    1 ThreadDemo td = new ThreadDemo();
    2 //通过FutureTask包装器来创建Thread线程,用于接收运算结果。
    3 FutureTask<Integer> result = new FutureTask<Integer>(td);
    4 new Thread(result).start();
    5 System.out.println(result.get());

    使用ExecutorServices.submit()方法调用它

    1 ThreadDemo td = new ThreadDemo();
    2 //利用ExecutorServices.submit()的方法调用
    3 ExecutorService exec=Executors.newCachedThreadPool();
    4 Future<Integer> results=exec.submit(td);
    5 System.out.println(results.get());

    它与Runnable的区别在于它有返回值,可以抛出异常。runnable无返回值,不能抛出异常。

    两种多线程实现方式的对比

    看一下Thread类的API:

    其实Thread类也是实现的Runnable接口。两种实现方式对比的关键就在于extends和implements的对比,当然是后者好。因为第一,继承只能单继承,实现可以多实现;第二,实现的方式对比继承的方式,也有利于减小程序之间的耦合。

    线程状态

    虚拟机中的线程状态有六种,定义在Thread.State中:

    1、新建状态NEW

    new了但是没有启动的线程的状态。比如"Thread t = new Thread()",t就是一个处于NEW状态的线程

    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()方法执行结束后,线程即处于终止状态。处于终止状态的线程不具备继续运行的能力。

  • 相关阅读:
    值传递
    抽象类
    面向对象三大特征(二)--继承
    单例设计模式
    神奇的main方法详解
    面向对象的三大特征 ---- 封装
    变量、方法以及静态和非静态
    面向对象编程-类和对象
    数组
    力扣题库刷题(随时记录)
  • 原文地址:https://www.cnblogs.com/xiaojiesir/p/10981082.html
Copyright © 2020-2023  润新知