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

posted @ 2019-08-18 17:57  xiaojiesir  阅读(221)  评论(0编辑  收藏  举报