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