并发对学习编程的人来说可谓是一道坎,迈不过这道坎以后学习更加深奥的东西也只是学其皮毛,难有大成。我就是当时没有打好基础,最后学习Java web、Java框架的时候对这些东西很难有深刻的理解,所以我不得不回头来恶补基础。今天我们就来谈谈并发那些事。
并发
并发就是将程序划分为多个任务,每个任务可以在同一时间内同时执行,与顺序执行相比大大缩短执行的时间。
在多处理机系统中情况确实如上所属,所有的任务在同一时刻分发给不同的cpu执行,这确实可以大幅度提高程序的执行效率。
但是在单处理机系统中我们却要换一个角度考虑并发。单处理机系统只有一个cpu,必然不能同时运行多个任务,这个时候任一时刻只能有一个任务在cpu中执行。这个时候的并发就是一个假象,它将一个cpu周期(cpu周期这个词用的不准确)划分为多个时间间隙,每一个时间间隙只运行一个任务,当这个时间间隙过完后就会将当前的任务保存状态保存,转而执行下一任务,所有任务交替执行,这样一来在这个周期内给人的感觉就是所有任务并发执行。
在单处理机系统下的这种交替执行从表面上看并不能提高任务的执行速度(与顺序执行相比),因为它要花费更多的时间用于上下文的切换,在一般情况下也确实如此。但是请你考虑这样一种情况,如果这些任务中的一些任务要请求输入(可能是其他io请求、网络请求等)这时如果没有输入给出就会发生阻塞,导致下面的任务不能被执行,要知道程序的运行速度是远比io的速度要快的多的,这个时候必然会浪费掉大量的等待时间。如果我们使用了多线程,遇到这种阻塞发生时调度程序就会将阻塞的线程移出cpu转而执行其他的任务,在执行其他任务的时间里等待阻塞的接触。这样看来,切换上下文所需要付出的代价还是值得的。
定义任务
线程驱动的对象是任务,我们要想使用多线程执行多任务,就必须先定义任务。常见的任务定义方式有三种:
- 实现Runnable接口
package com.mfs.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TaskWithRunnable {
public static void main(String[] args) {
// TODO Auto-generated method stub
//此处开启五个任务,即开启五个线程
for (int i = 0;i < 5; i ++) {
Thread t = new Thread(new Fb()); //调用Threa的构造器,将一个Ruannable对象转换为一个工作任务
t.start(); //调用start方法开始这个任务
}
System.out.println();
//除使用Thread构造器来创建线程任务外还可以使用Executor(执行器)创建线程池管理Thread对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); //创建线程池,还有一些其他的线程池,大家可以试验一下。
for (int i = 0; i < 5; i ++) { //向线程池中添加五个线程
cachedThreadPool.execute(new Fb()); //线程池会自动启动线程
}
cachedThreadPool.shutdown(); //关闭线程池,线程池中已经有的线程继续被执行,但是不能在添加新的线程进入线程池
}
}
class Fb implements Runnable {
private static int taskCount = 0;
private final int id = taskCount ++; //任务的id
@Override
public void run() { //实现Runable接口就必须实现此方法
// TODO Auto-generated method stub
int a = 0, b = 1;
for (int i = 0;i < 10; i ++) { //输出斐波那契数列的前十个数字
System.out.print("#" + id + "(" + b + ") ");
int t = b;
b = a + b;
a = t;
Thread.yield(); //建议任务调度器在此处进行任务切换,但只是建议而已,是否切换并不确定,所以我们不能指望他完成任何重要的功能
}
}
}
- 实现Callable接口
Callable接口与Runnable接口的不同之处在于,Callable接口要实现的方法是call方法,并且此方法能够产生返回值。
package com.mfs.thread;
import java.util.List;
import java.util.ArrayList;
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;
public class TaskWithCallable {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Future<String>> res = new ArrayList<>();
ExecutorService pool = Executors.newFixedThreadPool(5); //FixedThreadPool允许事先指定可以开启的线程的最大值
for (int i = 0; i < 5; i ++) {
Future<String> future = pool.submit(new Fb1()); //返回一个Future对象
res.add(future);
}
/*
* 可以看到在我们已经添加了五个任务后仍然能够在向pool中添加任务
* 但是这个任务却不能立即执行,只有等pool中的一个任务终止之后才能启动此任务
*/
res.add(pool.submit(new Fb1()));
pool.shutdown();
for (Future<String> f : res) {
try {
/*
* 调用get能够返回对应任务的返回值。
* 如果此时对应任务还没有执行完毕,返回值也就没有准备完毕,此时会阻塞程序,直到返回值准备完成
* 我们也可以在get前使用isDone方法判断是否准备完毕
* get方法也有指定超时等待的版本
*/
String s = f.get();
System.out.println(s);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/*
*还是输出斐波那契数列的前十位,不同的是要在10位数全部输出后返回一个任务结束的字符串
*泛型指定返回值的类型
*/
class Fb1 implements Callable<String> {
private static int taskCount = 0;
private final int id = taskCount ++;
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
int a = 0, b = 1;
for (int i = 0; i < 10; i ++) {
System.out.print("#" + id + "(" + b + ") ");
int t = b;
b = a + b;
a = t;
}
return "#" + id + "任务结束";
}
}
- 继承Thread类
这种方法实现人物的定义比较简单,但我们的任务不是很复杂是确实是一个行之有效的方法。但是继承的局限性我们都知道,所以一般来说是不太常用这种方法的。
package com.mfs.thread;
public class TaskWithThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
for (int i = 0; i < 5; i ++) {
new Fb2().start(); //直接创建对象调用Threa就好了,不能使用线程池
}
}
}
class Fb2 extends Thread {
private static int taskCount = 0;
private final int id = taskCount ++;
@Override
public void run() {
// TODO Auto-generated method stub
int a = 0, b = 1;
for (int i = 0; i < 10; i ++) {
System.out.print("#" + id + "(" + b + ") ");
int t = b;
b = a + b;
a = t;
}
}
}