java并发——四种创建线程方式

并发概念

并发用来提高运行在单处理器上的程序的性能。

这听起来有些违背直觉。如果有多个CPU处理器,那么我们让不同CPU并发处理程序一定会让速度变快。但是我们只有一个处理器,并发看起来只会增加上下文切换的开销时间。真的是这样吗?让这个问题的答案反转的是:阻塞。如果我们在执行一段代码中,有一处发生了阻塞,我们只能将整个程序停下来。如果我们采用并发的方式,即使这一处发生了阻塞,其他的任务还可以继续执行,直到程序结束,最后的情况只是一处阻塞,结果还不算太坏。

事实上,从性能的角度看,如果没有任务会阻塞,那么在单处理器上使用并发就没有任何意义。 ——Bruce Eckel

实现并发最直接的方式就是我们的操作系统做的那样,使用进程。在多任务操作系统中可以通过周期性将CPU从一个进程切换到另一个进程,来实现同时运行多个进程的效果。尽管这样会让进程看起来执行的停停歇歇,但是互不干扰的进程还是非常吸引人。不同的,JAVA中使用线程来实现并发的概念。不同的线程会共享一个进程下的资源和I/O设备,这使得如何控制访问的同步变成了重要的课题。

在单CPU处理器上使用并发程序在任何时刻都只是执行一项工作,因此从理论上讲,肯定可以不用任何任务而编写出相同的程序。但是并发提供了一个重要的组织结构上的好处:类似仿真等程序的设计可以极大地简化。最浅显的例子,比如我们做超级马里奥的小游戏,采用多线程来控制马里奥和怪物的行为就比不使用方便的多。如果在游戏中还有类似菜单的按钮,我们不可能每段代码都去检查这个按钮是否被点击,这个时候使用一个线程就显得方便简洁。

线程状态

在Thread中的内部嵌套类State中规定,线程一共有6种状态。

      New 新创建的线程

      这里的创建新的线程真的是仅仅new了一个线程。创建新的线程,是指刚new出来的线程,这个线程没有通过start的方法来启动。

      Runnable 可运行

    一旦我们调用了start方法,这个线程开始工作并处于可运行状态。可运行状态不只包含线程运行,线程中断也被算为可运行状态。一个可运行状态的线程可能在运行也可能没在运行,不要因线程在可运行的状态下没运行而急躁,很有可能这个线程的终止只

    是为了让其他的线程获得机会。

    Blocked 被阻塞


    一个线程试图去获得一个内部锁时,但这个内部锁被其他的线程持有,这个时候,为了等待去使用这个内部锁,这个线程将会暂时处在被阻塞的状态。当其他线程释放锁的时候,这个线程获得了内部锁,并且从阻塞状态转变为非阻塞状态。

    Wait 等待

    一个线程等待另一个线程通知调度器一个条件(condition),这个线程自己进入等待状态。等待状态和阻塞状态很类似,但是他们是存在本质区别的。如果另一个线程通知调度器结束,那么这个线程进行工作,等待状态也随之结束。

    Timed waiting 计时等待


    计时等待和等待是比较相似的,计时等待相比较等待多了一个超时参数。调用他们导致线程会进入计时等待。这个状态将一直保持到超时期满或者接收到适当的通知。相比较直接的等待,变得更加的安全。

    Terminated 终止


    线程终止。线程run方法执行方法体中最后一条语句后,正常退出而自然死亡。或者,出现了在方法中没有捕获的异常,此时终止run方法意外死亡。

创建线程

1.Thread创建线程

第一种创建线程的方式是从Java.lang.Thread类派生一个新的线程类,重载它的run()方法。ExtThread类是实现了Thread的一个子类,它重写了run方法。NewThread类是主方法,创建了一个ExtThread的实例,并且通过调用start方法启动了该线程,自动调用了run方法。我们不需要手动调用run方法,而应该调用start方法来让它自动调用run方法。在JAVA的API中,start是这样定义的:

public void start( ) 

使该线程开始执行;

Java 虚拟机调用该线程的 run 方法。

结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。

多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。

如果我们直接调用run方法,得到的将是在main函数的主线程中调用的run方法,这样没有开启新的线程。

run方法通常会以某种形式的循环来进行,使得任务一直运行下去直到不再需要,所以要设定跳出循环的条件(或者直接从run方法返回)。通常run方法被写成无限循环的形式,这样就意味着,除非某个条件使得run终止,否则他将永远运行下去。

NewThread类:

    package AllThread;

    /**

    *

    * @author QuinnNorris

    *

    *        通过Thread创建新线程

    */

    public class NewThread {

        /**

          * @param args

          */

          public static void main(String[] args) {

              // TODO Auto-generated method stub

              Thread t1 = new ExtThread();

              // 创建一个ExtThread对象

              t1.start();

              // 调用start方法,运行新的线程,即运行的是t1中的run方法

          }

    }

ExtThread类:

      package AllThread;

      /**

      *

      * @author QuinnNorris

      *

      *        Thread的一个实现类

      */

      public class ExtThread extends Thread {

            @Override

            public void run() {

            // TODO Auto-generated method stub

            System.out.println("new thread");

            }

      }

2.实现Runnable接口创建线程

在JAVA中类仅支持单继承。这样,如果创建自定义线程类的时候是通过扩展 Thread类的方法来实现的,那么这个自定义类就不能再去扩展其他的类,也就无法实现更加复杂的功能。因此,如果自定义类必须扩展其他的类,那么就可以使用实现Runnable接口的方法来定义该类为线程类,这样就可以避免Java单继承所带来的局限性。

NewRunable类:

    package AllThread;

    /**

    *

    * @author QuinnNorris

    *

    *        通过Runnable接口创建新线程

    */

    public class NewRunable {

        /**

          * @param args

          */

        public static void main(String[] args) {

              // TODO Auto-generated method stub

              Runnable r = new ImplRunnable();

            // 创建一个Runnable实例类的对象

            Thread t1 = new Thread(r);

            // 由r作为构造器的参数创建一个Thread对象

          // 将这个Runnable子类对象作为参数传入Thread中

          t1.start();

          // 调用start方法,运行新的线程,即运行的是t1中的run方法

          }

    }

ImplRunnable类:

    package AllThread;

    /**

    *

    * @author QuinnNorris

    *

    *        Runnable的一个实现类

    */

    public class ImplRunnable implements Runnable {

        @Override

        public void run() {

            // TODO Auto-generated method stub

            System.out.println("new thread");

          }

      }

多线程资源共享

我们可以利用实现Runnable接口的方式实现多线程的资源共享:把资源保存在Runnable接口中,只创建一份实现了Runnable接口的类的实例,多个Thread对象用同一个Runnable接口实例为参数实例化。但是要注意的是,资源的共享会涉及到同步的问题,如果处理不当,那么数据的谬误、脏数据的出现是必然的事情。每当涉及到资源共享时都要小心谨慎。而且这种资源共享的方法也不是必须的,只要我们能确保最后所有的线程都指向同一个资源,那么他的存放位置不需要被严格规定。

3.使用执行器(Executor)创建线程池(thread pool)

使用线程池是比前两种相对少见的创建线程做法。从JAVA SE5开始,java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。如果我们的程序需要用到很多生命周期比较短的线程,那么应该使用线程池,线程池中包含了很多空闲线程,而且这些线程的生命周期不需要我们操心。另一个使用线程池的原因是:如果你的代码需要大量的线程,那么最好使用一个线程池来规定总线程数的上线,防止虚拟机崩溃。这样可以限制最大的并发数量。

静态方法创建线程池实例

正如Collection类的静态方法都在Collections中一样,执行器Executor创建线程池的静态方法全部在Executors类中:

      public static ExecutorService newCachedThreadPool()

创建一个新的线程池。如果需要线程而线程池中无空闲线程时,创建一个新的线程。空闲线程会被保留60秒。

      public static ExecutorService newFixedThreadPool(int nThreads)

根据参数值创建一个固定数量线程的线程池。如果所需线程超过池中线程数则会发生等待。空闲线程会被一直保留。

      public static ExecutorService newSingleThreadExecutor()

创建一个仅有一个线程的线程池,顺序执行每一个提交的任务。(和第二种方法参数为1时效果相同)

提交Runnable任务到线程池中

Executor作为一个祖先接口,提供了一个也仅有一个提交线程的方法:

      void execute(Runnable command)

在Executor的子类中,有很多子类提供了具有返回值的提交方法,返回提交的结果。

      public Future<?> submit(Runnable task)

比如这种submit方法,提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功 完成时将会返回 null。调用get方法就能得到提交的结果。

关闭线程池

在线程使用结束后,为了保证程序的安全,我们有必要手动调用关闭线程池的方法:

      public void shutdown()

ThreadPool类:

      package AllThread;

      import java.util.concurrent.ExecutorService;

      import java.util.concurrent.Executors;

      /**

      *

      * @author QuinnNorris

      *

      *        创建线程池

      */

    public class ThreadPool {

      /**

        * @param args

        */

        public static void main(String[] args) {

            // TODO Auto-generated method stub

            ExecutorService es = Executors.newFixedThreadPool(5);

            // 我们调用静态方法创建了包含五个线程的线程池

            for (int i = 0; i < 5; i++)

                es.submit(new ImplRunnable());

            es.shutdown();

            // 在使用结束之后,一定要关闭线程池

        }

      }

ImplRunnable类:

    package AllThread;

    /**

      *

      * @author QuinnNorris

      *

      *        Runnable的一个实现类

      */

    public class ImplRunnable implements Runnable {

        @Override

        public void run() {

            // TODO Auto-generated method stub

            System.out.println("new thread");

        }

    }

4.使用Callable与Future创建线程并获取返回值

我们使用Runnable封装了一个异步运行的任务,我们可以把它想象成一个没有参数和返回值的异步方法,Callable与Runnable相似,但是Callable具有返回值,可以从线程中返回数据。

Callable

我们从jdk中找到了Callable<V>的源代码,去掉一些无用的部分:

    package java.util.concurrent;

    public interface Callable<V> {

              V call() throws Exception;

    }

可以看出Callable接口只是将run方法换成了call方法,其他并没有太多的改动。

Future

Future类负责保存异步计算的结果。可以启动一个计算,将Future对象交给某个线程,然后我们去做其他的事情,Future对象的所有者在结果计算好之后就可以调用get方法获得它。我们在上面线程池的submit方法中也提到过。

V get() throws InterruptedException,ExecutionException

如有必要,等待计算完成,然后通过get方法获取其结果。

FutureTask包装器

我们虽然有了Callable和Future类,但是我们仍然需要一种方法将他们结合起来使用。而且还存在的问题是,Callable的出现替代了Runnable。我们需要一种手段让Thread类能够接受Callable做参数。在这里我们使用非常好用的FutureTask包装器。它可以将Callable转换成Futrue和Runnable,因为他同时实现了Runnable和Future<V>两个接口。

CallablePool类:

    package AllThread;

    import java.util.ArrayList;

    import java.util.concurrent.ExecutionException;

    import java.util.concurrent.ExecutorService;

    import java.util.concurrent.Executors;

    import java.util.concurrent.Future;

    import java.util.concurrent.FutureTask;

    /**

      *

      * @author QuinnNorris

      *

      *        用线程池实现Callable创建线程

      */

    public class CallablePool {

        /**

          * @param args

          */

        public static void main(String[] args) {

            // TODO Auto-generated method stub

            ExecutorService es = Executors.newFixedThreadPool(5);

            // 创建一个5个线程大小的线程池

          ArrayList<Future<Integer>> results = new ArrayList<Future<Integer>>(5);

          // 创建一个Future<Integer>类型的数组

          FutureTask<Integer> ft = null;

          for (int i = 0; i < 5; i++) {

              ft = new FutureTask<Integer>(new ImplCallable(i));

              // 将Callable类型转化成FutureTask类型

              es.submit(ft);

              // 提交线程

              results.add(ft);

              // 将返回的结果提交,因为FutureTask同时也可变为Future类型,所以这里不需要其他类型转化

          }

          for (int i = 0; i < 5; i++)

              try {

                System.out.println(results.get(i).get());

                // 打印结果,发现数组中为0到4五个数字,成功。

              } catch (InterruptedException e) {

                  // TODO Auto-generated catch block

                  e.printStackTrace();

              } catch (ExecutionException e) {

                  // TODO Auto-generated catch block

                  e.printStackTrace();

              }

          }

        }

ImplCallable类:

    package AllThread;

    import java.util.concurrent.Callable;

    /**

      *

      * @author QuinnNorris

      *

      *        Callable的实现类

      */

    public class ImplCallable implements Callable<Integer> {

        private int index;

        ImplCallable(int index) {

              this.index = index;

        }

      @Override

      public Integer call() throws Exception {

          // TODO Auto-generated method stub

          return index;

      }

    }

上面采用了线程池的方法来表现Callable和Future的使用方法,如果是简单实用Thread道理也是相同的,我们需要把:

ExecutorService es = Executors.newFixedThreadPool(5);

es.submit(ft);

这两句去掉,在for循环中替换成下面两句。创建Thread实例,开启新的线程。

Thread th = new Thread(ft);

th.start();

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容