`
JackyCheng2007
  • 浏览: 250125 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

深入学习 Java concurrent - 多线程

    博客分类:
  • Java
阅读更多
我觉得要清理一下关于进程和线程的基本概念。

首先就是进程。

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


那么什么是多线程呢?

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

那么为什么还要划分比进程粒度更小的执行过程呢?
在现在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");
	}
}

你肯定知道输出是什么,这里句话肯定会顺序输出:
引用

I am a task running
I am in main

这个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");
	}
}

你认为上面的代码会输出什么?
引用

I am in main
I am a task running

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

Executor
执行器,是一个简单的标准化的接口,提供线程似的子系统,比如线程池,异步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的工厂方法。

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




分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics