并发编程——基础部分(篇一)
这几天在研究Java中的线程机制,结果越是看,反而感觉越是掌握的太少,变得多疑而又自信,因为Java线程机制教会我,“理论上是可靠的,实际是不准确的。”,一直向前走,越会发现精彩而又迷惑的地方,这就是Java中的线程机制这几天所带给我的一些东西,虽然只是研究了很小一部分,但是感觉也很有收获,所以在这里将自己的读书笔记或是可以称为收货记录下来。
凡事都可以问一个为什么,那么我们也可以在这里一路为什么下去。
-
为什么要使用并发?
当第一次多线程这个概念进入我们脑海的时候,我们没有问过为什么会存在这个,也没有想过,但是我们现在需要考虑这个问题,这是为什么呢?多线程的效果是什么,是可以同时让多个任务同时执行,那么我们自然可以想到,比起所有的任务顺序执行,同时执行多个任务会更节省时间。但是如果我的机器只有一个处理器,还会节省时间吗?答案是否定的,因为我们知道处理器一次只能执行一条指令,那么如果是多线程的话,为了达到效果,那么在处理器上肯定会存在线程之间的切换,那么这与所有的任务顺序执行相比,是变得慢了,而不是更快。但是为什么我们还选择多线程呢?因为存在阻塞!这才是问题的核心,对于我们使用的用户界面,如果不使用多线程,那么怎么可能事实响应用户的输入,而且又在做着处理别的事情的工作呢,因此这才是多线程出现比较重要的原因,而且如果我们企图通过在顺序执行中去实时检测用户输入,或是什么,那么我不敢想我们会将程序写的有多么复杂。
- 为什么是线程而不是进程?
实现并发的最直接的方式是在操作系统级别使用进程,进程是运行在自己的地址空间的自包容程序。多任务操作系统可以周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个程序。操作系统会将进程之间进行隔离,它们之间互不干涉,这使得进程编程相对容易一些。与此相反,Java所使用的这种并发系统会共享诸如内存和I/O这样的资源,因此编写多线程程序最基本的困难在于协调不同线程驱动的任务之间对这些资源的使用,以使得这些资源不会同时被多个任务访问。进程之间虽然隔离,是并发的理想模型,但是进程通常会有数量和开销的限制,以避免它们在不同的并发系统之间的可用性。Java中的线程机制是在由执行程序表示的单一进程中创建任务。
- 来一点线程的干货
Java的线程机制是抢占式的,因此我们永远不知道下一次执行的会是谁?这表示调度机制会周期性地中断线程,切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都会分配到数量合理的时间去驱动任务。但是在协作式系统中,每个任务都会自动地放弃控制,这要求在程序语句中插入某种让步语句。协作式系统的优势是双重的:上下文切换的开销通常比抢占式系统要低廉许多,并且可以同时执行的线程数量在理论上是没有限制的。并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立任务中的每一个都将由执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此单个进程可以拥有多个并发执行的任务,但是程序使得每个任务都好像有其自己的CPU一样,其底层机制是切分CPU时间。线程模型为编程带来了便利,简化了在单一程序中同时交织在一起的多个操作的处理。使用线程时,CPU将轮流给每个任务分配其占用时间。多任务和多线程往往是使用多处理器系统的最合理方式。
- 如何创建多任务程序?
1、实现Runnable接口
首先我们需要来描述任务,这可以由实现Runnable接口来提供,只需要实现Runnable接口,编写里面的run()方法将你的任务添加进去即可,然后显式将任务附着到一个线程上,就是将Runnable对象交给Thread构造器。然后启动Thread对象调用start()方法,这样就可以与其它线程“同时”执行了。如下:
- class MTask implements Runnable{
- /**
- * 实现接口里面的run()方法
- */
- public void run() {
- System.out.println("为人民服务!");
- }
- }
- public class MyTask {
- public static void main(String[] args){
- Thread thread = new Thread(new MTask());
- thread.start();
- }
- }
上面首先类MTask实现接口Runnable,在接口方法run()里面描述需要执行的任务(打印字符串),然后在下面的类中,创建了一个线程对象,将任务附着到这个线程上,调用线程启动的方法,就可以实现了。
2、继承Thread类
继承Thread,虽然我们可以通过继承Thread类,来实现并发编程,但是由于Java中的单根继承,因此当我们继承了Thread类时就不能继承其它的类,这有时会是一种限制。如下:
- public class MyTask extends Thread{
- public static void main(String[] args){
- MyTask mt = new MyTask();
- mt.start();
- }
- /**
- * 重写Thread中的run()方法
- */
- public void run(){
- System.out.println("为人民服务!");
- }
- }
如上,可以直接继承Thread类,在里面重写run()方法,来编写需要实现的任务(仍是打印字符串),然后创建该类的对象,执行。
我们可以发现,不管是实现接口Runnable还是继承Thread,最后启动都是调用start()方法,通过start()方法来启动线程的根本原因是,线程总是由操作系统来占有和管理,一个新的线程只能由操作系统来创建和启动,如果自己直接调用run()方法,那么这和其他方法有什么区别呢,还是在调用的线程中执行。
- 使用Executor
Java.util.concurrent包中的执行器(Executor)将为我们管理Thread对象。从而简化并发编程,Executor在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。ExecutorService对象是使用静态的Executor方法创建的。使用静态方法可以创建不同类型的Executor,有(下面仅列出三种)
CachedThreadPool(会在执行过程中创建与所需数量相同的线程,回收旧线程时停止创建新的线程);
FixedThreadPool(可以预先一次性执行代价高昂的线程分配);
SingleThreadExecutor(就像线程数量为1的FixedThreadPool,如果向它提交多任务,那么这些任务将排队,每个任务都会在下一个任务开始前结束,所有任务使用相同的线程,它提供了一种重要的并发保证,这会改变任务的加锁需求)
1、使用CachedThreadPool
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- class Task implements Runnable{
- /**
- * 编写需要执行的任务
- */
- public void run() {
- System.out.println("为人民服务!");
- }
- }
- public class MyTask extends Thread{
- public static void main(String[] args){
- ExecutorService ecs = Executors.newCachedThreadPool();
- ecs.execute(new Task());
- ecs.shutdown();
- }
- }
其中ExecutorService对象通过Executors类的静态方法获得,提供CachedThreadPool,然后我们只需要将创建的新任务交给ecs对象,调用execute()方法即可,那么我们不需要显式创建以及启动线程,因此这就好比我们需要运输东西,没有必要自己再造个车(Thread)来进行运输,只需要将运输任务交给Executor就好,来负责管理Thread。其中后面调用shutdown()方法是为了防止继续给ecs提交新的任务。FixedThreadPool和SingleThreadExecutor的使用和上面很类似。如下:
2、使用FixedThreadPool
使用FixedThreadPool预先一次性执行制定数目的线程分配,假如我们指定分配3个线程,提交多余3个的任务,那么只有3个线程在运行这些任务,示例如下:
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.ThreadFactory;
- class Task implements Runnable{
- /**
- * 编写需要执行的任务
- */
- public void run() {
- System.out.println(Thread.currentThread().getName() + "为人民服务!");
- }
- }
- class M_ThreadFactory implements ThreadFactory{
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- System.out.println("创建了线程:" + t.getName());
- return t;
- }
- }
- public class MyTask extends Thread{
- public static void main(String[] args){
- ExecutorService ecs = Executors.newFixedThreadPool(3, new M_ThreadFactory());
- for(int i = 0; i < 10; i++){
- ecs.execute(new Task());
- }
- ecs.shutdown();
- }
- }
在上面的程序中,忽视其中的类M_ThreadFactory,那个只是用来为了方便证明Executor中只有指定个数个线程(在函数中指定的是3),看一下输出结果,就很明白,如下:
创建了线程:Thread-1
Thread-0为人民服务!
Thread-1为人民服务!
创建了线程:Thread-2
Thread-2为人民服务!
Thread-2为人民服务!
Thread-2为人民服务!
Thread-2为人民服务!
Thread-2为人民服务!
Thread-2为人民服务!
Thread-2为人民服务!
Thread-2为人民服务!
可以明显看到,里面只有Thread-0、Thread-1和Thread-2,这三个线程。
由于SingleThreadExecutor可以视为这个的指定线程数为1的特殊情况,因此这里不进行演示。
现在时间不是很早了。。。。。。写到这里打住,前面这些只是并发方面的一些比较基础的东西,后面还有很多。。。。。。嘿嘿
未完,待续!