深入学习 Java concurrent - 多线程

我觉得要清理一下关于进程和线程的基本概念。

[size=medium][color=blue][b]首先就是进程。[/b][/color][/size]

什么是进程呢?简单的说就是cpu的一个执行过程,在这个过程中,os为它分配独立的内存空间,这样就保证不被别的进程影响。单任务的os就是说多个进程间只能顺序的执行,也就是说多个事情只能一件一件的做。那么多任务的os支持多个进程交叉同时执行。为什么要这样呢?学过统筹方法吗?如果没有,你是不是经常同事做两件事情?比如在打开电脑的同时,你在吃早点。cpu也是一样的,比如当cpu通过打印机打印的时候,让打印机自己忙去,cpu还可以同时干点别的事情。这样就理解多进程的原因了。


[size=medium][color=blue][b]那么什么是多线程呢?[/b][/color][/size]

线程也是一个执行过程,只是比进程的粒度更小。在一个进程中可以同时多个线程交叉进行。不同的是,线程并没有向进程那样享有单独的地址空间,而是给你共享这个进程所在的地址空间。

那么为什么还要划分比进程粒度更小的执行过程呢?
在现在os中,支持线程调度,那么线程就成了任务调度的单位。不过进程确实资源分配的单位。
多进程系统可以同时运行多个进程,但是每一个进程只能有一个线程。多线程系统允许运行多个线程,但是统一进程内的所有的线程共享相同的地址空间和系统资源。

不管多进程也好,多线程也好都是为了并发。并发的好处除了加快处理任务外,还有一个好处就是为了更好的设计。比如当你copy文件的时候,你不想copy了,你可以中途取消,这就是并发带来的好处,这个需求是很简单直观的,但如果没有并发技术的支持,在计算机技术里面是很难实现的。没有并发,计算机就会变的一根筋。

在Java里面又是怎么做的呢?
首先,既然是多任务同时进行,得先定义任务。在Java里面定义一个任务很简单,只要让这个类实现Runnable 接口。同时实现这个接口的唯一的一个run()方法。这个有点类似command模式。在run方法里面定义这个任务要完成的事情。

public class HelloTask implements Runnable {
@Override
public void run() {
System.out.println("I am a task running");
}

public static void main(String[] args) {
HelloTask task = new HelloTask();
task.run();
System.out.println("I am in main");
}
}

你肯定知道输出是什么,这里句话肯定会顺序输出:
[quote]
I am a task running
I am in main
[/quote]
这个Runnable接口实在没有什么特别的:

public interface Runnable {
public abstract void run();
}


你看,和普通的接口有什么区别?在main方法里面调用,就和一般的类没有什么区别。只是它总是和多线程绑在一起,才显得神秘。既然没有什么特别的,那就怎么让它实现并发呢?那就得把这个task挂在一个线程上:

public class HelloTask implements Runnable {
@Override
public void run() {
System.out.println("I am a task running");
}

public static void main(String[] args) {
HelloTask task = new HelloTask();
Thread t = new Thread(task);
t.start();
System.out.println("I am in main");
}
}

你认为上面的代码会输出什么?
[quote]
I am in main
I am a task running
[/quote]
这两句话,不一定哪个先输出。这就是并发导致的,当调用start方法的时候,就会启动一个新的线程和当前正在运行的主线程并发执行,他们会争抢cpu,看谁快。所以输出的顺序无法预知。
你因该注意到了,在构造Thread的时候我们把task传给了他,那时因为它接收Runnable接口。他会在start方法之后调用这个参数对象的run方法。这是不是command模式?
所以说,任何类都可以放在Thread里面去并发运行,只要实现Runnable接口,编写自己的run方法,高速线程,你想干什么。说道这里视乎线程也没有那么什么了,只是他会并发的调用task的run方法。
从Jdk5以后,jdk给我们提供过来更方便的多线程实现工具包java.util.concurrent提供了一些有用的并发编程框架和工具类。

[size=medium][color=blue][b]Executor[/b][/color][/size]
执行器,是一个简单的标准化的接口,提供线程似的子系统,比如线程池,异步IO以及轻量级的任务框架。


public class HelloTask implements Runnable {
private static int taskCount = 0;
private final int id = taskCount++;

@Override
public void run() {
System.out.println("I am a task running: " + id);
}

public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.execute(new HelloTask());
}
exec.shutdown();
System.out.println("I am in main");
}
}

有了这个包,你不用再自己管理Thread的生命周期。而是托管给ExecutorService。ExecutorService可以帮你调度并发任务,你只要调用它的execute方法,你一下他的execute方法,也是按照Command模式设计的。ExecutorService 也是一个接口,并且实现了Executor接口。除了包含Executor方法外,ExecutorService 还定义了其他一些方法:

public interface ExecutorService extends Executor {
void shutdown();
<T> Future<T> submit(Callable<T> task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
...
}
public interface Executor {
void execute(Runnable command);
}

Executors是一个静态工厂类,提供了很多创建各种ExecutorService的工厂方法。

[size=medium][color=blue][b]线程池[/b][/color][/size]
如果某些资源的创建比较费劲,也就耗费时间和资源,那就得考虑用pool也就是池来保存一些预先创建好的资源,用的时候拿来用,用完放回去。典型的有jdbc连接池。线程池也算一个。
Executors就提供了各种各项的线程池共你选择:
[b]1. CachedThreadPool[/b]
这种线程池会不停的为你创建新的线程,只要当前pool里面没有可用的。这个其实很危险,很容易耗尽你的资源。
[b]2. FixedThreadPool[/b]
这个线程池的策略是为你保持指定大小的线程数,这个还算比较好控制,任务比线程数多的时候就排队等候。
[b]3. SingleThreadExecutor[/b]
其实就是固定大小为一的FixedThreadPool。这个策略也很有用。当你只想用一个线程完成一系列任务的时候,就可以用它为你维护你可以任务队列,他会帮你一次顺序调用。完成一件,在做下一件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值