java线程的创建

定义任务     
     在java中使用任务这个名词来表示一个线程控制流的代码段,用Runnable接口来标记一个任务,该接口的run方法为线程运行的代码段。
  1. public class LiftOff implements Runnable {
  2. protected int countDown = 10;
  3. private static int taskCount = 0;
  4. private final int id = taskCount++;
  5. public void run() {
  6. while(countDown-- > 0 ) {
  7. System.out.print("#"+id+"("+countDown+"). ");
  8. Thread.yield();
  9. }
  10. }
  11. }
     Thread.yield()的调用时对线程调度器的一种建议,意思是如今是如今愿意主动放弃cpu时间片的占用, 交于其它线程使用。
     如今我们使用该类。
  1. public class MainThread {
  2. public static void main(String[] args) {
  3. LiftOff lauch = new LiftOff();
  4. lauch.run();
  5. }
  6. }
     
        上面的使用方法是在主线程中直接调用了该类对象的run方法,而并不是创建了一个新的线程运行该任务。要想实现线程行为,你必须显式的将一个任务附着到线程上。在java中使用Thread类来创建一个线程。
  1. public class BasicThreads {
  2. public static void main(String[] args) {
  3. Thread t = new Thread(new LiftOff());
  4. t.start();
  5. System.out.println("Waiting for LiftOff");
  6. }
  7. }
     
       一个线程仅仅能相应一个任务,可是一个任务能够被多个线程所运行。在一个任务被多个线程运行的情况下,该任务内的成员对象也被多个线程共享。如:
  1. public class MoreBasicThreads {
  2. public static void main(String args[]) {
  3. LiftOff liftOff = new LiftOff();
  4. for(int i = 0; i < 5; i++) {
  5. new Thread(liftOff).start();
  6. }
  7. }
  8. }
   
 
      能够看到输出结果是很怪异的:顺序颠倒了,且"5"直接消失了。在这里是因为countDown的自减操作与输出之间的空隙会被其它线程插入,以及对于taskCount变量还存在可见性的问题(jvm会对其进行优化,导致每次操作并不是都在内存中进行,所以每一个线程所示变量状态是不一致的)。程序每次的执行结果都会不同,这样的现象被称为线程竞速。在后面的文章中,我们将介绍线程间怎样安全的协作。
     (在这里,不再介绍以继承Thread方式来定义任务并启动线程的方式,由于那种方式受继承及线程间不能在任务内共享资源等问题的局限。)
     

Executor

     JDK5为我们提供了Executor(线程运行器)工具来帮助我们管理线程,借助该工具,我们能够高效管理多个线程。
经常使用有三种行为的Executor。
使用CachedThreadPool的Executor
     CacheThreadPool将为每一个任务都创建一个线程,该线程池中线程的数量没有上限。假设在该线程池中的一个线程执行结束,那么该线程将被线程池回收等待下次创建新线程使用。例:
  1. public class CachedThreadPool {
  2. public static void main(String []args) {
  3. ExecutorService exec = Executors.newCachedThreadPool();
  4. for(int i = 0 ; i < 5; i++)
  5. exec.execute( new LiftOff() );
  6. exec.shutdown();
  7. }
  8. }
/*Output :(Sample)
#1(9). #1(8). #1(7). #1(6). #1(5). #1(4). #3(9). #4(9). #2(9). 
#0(9). #2(8). #4(8). #3(8). #3(7). #3(6). #3(5). #3(4). #3(3). 
#3(2). #3(1). #3(0). #1(3). #4(7). #4(6). #4(5). #2(7). #2(6). 
#0(8). #0(7). #0(6). #0(5). #0(4). #0(3). #0(2). #0(1). #0(0). 
#4(4). #1(2). #4(3). #1(1). #4(2). #1(0). #4(1). #4(0). #2(5). 
#2(4). #2(3). #2(2). #2(1). #2(0). 
     对shutdown()方法的调用能够防止新任务被提交给这个Executor,可是已提交的任务会继续执行直到完毕。

使用FixedThreadPool的Executor
     FixedThreadPool会预先定制好线程池的容量大小(线程数量),即在创建该线程池的时候线程已经被分配,后面不再同意线程数量的扩充。使用这样的线程池能将代价高昂的线程分配的工作一次性运行完毕,而且避免你滥用线程。创建线程例:
  1. public class FixedThreadPool{
  2. public static void main(String []args) {
  3. ExecutorService exec = Executors.FixedThreadPool();
  4. for(int i = 0 ; i < 5; i++)
  5. exec.execute( new LiftOff() );
  6. exec.shutdown();
  7. }
  8. }

SingleThreadExecutor
     该Executor仅仅使用一个线程,就像是线程数量为1的FixedThreadPool。假设向SingleThreadExecutor提交了多个任务,那么这些任务将排队,每一个任务都会在下一个任务開始之前执行结束,全部的任务都将使用同样的线程。
  1. public class SingleThreadExecutor {
  2. public static void main(String []args) {
  3. ExecutorService exec = Executors.newSingleThreadExecutor();
  4. for(int i = 0 ; i < 5; i++)
  5. exec.execute( new LiftOff() );
  6. exec.shutdown();
  7. }
  8. }

/*Output :
#0(9). #0(8). #0(7). #0(6). #0(5). #0(4). #0(3). #0(2). 
#0(1). #0(0). #1(9). #1(8). #1(7). #1(6). #1(5). #1(4). 
#1(3). #1(2). #1(1). #1(0). #2(9). #2(8). #2(7). #2(6). 
#2(5). #2(4). #2(3). #2(2). #2(1). #2(0). #3(9). #3(8). 
#3(7). #3(6). #3(5). #3(4). #3(3). #3(2). #3(1). #3(0). 
#4(9). #4(8). #4(7). #4(6). #4(5). #4(4). #4(3). #4(2). 
#4(1). #4(0). 

     SingleThreadExecutor可以提供一定程度上的并发保证,即假设一个任务仅仅被该类型的Exector所运行的话,那么便不会存在线程竞速的问题。对于逻辑上独立的任务,而并不是性能要求须要使用线程的情况下,使用该Executor来管理线程是不二的选择。

从任务中产生返回值

     Runnable是运行工作时的独立任务,可是它不返回不论什么值。假设你希望任务在完毕时可以返回一个值,使用Callable接口而不是Runnable接口。Callable是一种具有类型參数的泛型,它的类型參数表示的是从方法call()(而不是run())中返回的值,而且必须使用ExecutorService.submit()方法调用它,以下是一个演示样例:
  1. class TaskWithResult implements Callable<String> {
  2. private int id;
  3. public TaskWithResult(int id) {
  4. this.id = id;
  5. }
  6.  
  7. @Override
  8. public String call() throws Exception {
  9. return "result of TaskWithResult " + id;
  10. }
  11. }
  12.  
  13. public class CallableDemo {
  14. public static void main(String[] args) {
  15. ExecutorService exec = Executors.newCachedThreadPool();
  16. ArrayList<Future<String>> results = new ArrayList<Future<String>>();
  17. for(int i = 0; i < 10;i++) {
  18. results.add( exec.submit(new TaskWithResult(i)));
  19. }
  20. for(Future<String> fs : results)
  21. try {
  22. System.out.println(fs.get());
  23. } catch (InterruptedException e) {
  24. System.out.println(e);
  25. } catch (ExecutionException e) {
  26. System.out.println(e);
  27. }
  28. exec.shutdown();
  29. }
  30. }

     submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了參数化。你能够用isDone()方法来查询Future是否已经完毕。当任务完毕时,它具有一个结果,你能够调用get()方法来获取该结果。你能够不用isDone()方法进行检查就直接调用get(),在这样的情况下,get()将堵塞,直至结果准备就绪。你还能够在试图调用get()来获取结果之前,先调用具有超时get(),或者调用isDone()方法来查看任务是否完毕。
     对于Callable应该要知道两点:1. 使用Runnable来创建的线程在执行中产生的对象或数据通常会使用额外的对象进行管理,而使用Callable产生的数据或对象能够不使用额外对象管理,线程的执行结果直接通过Future的get方法返回。2. 产生的Future对象的get方法具有堵塞的特性,所以能够利用此方法进行一些协作的操作,从而避免引入一些其它的同步设施,如原子锁等。

休眠

     Thread.sleep(long)函数可使调用该函数的线程休眠,单位为毫秒。除了这样的方式,还能够TimeUnit类进行睡眠,如TimeUnit.MILLISECONDS.sleep(1000),这种方法同意你指定sleep()延迟的时间单元,因此能够提供更好的可阅读性。

优先级

     线程的优先级将该线程的重要性传递给了调度器。调度器将倾向于将优先权最高的线程先运行,优先权较低的线程运行的频率较低。在绝大多数时间里,全部的线程都应该以默认的优先级运行。试图操纵线程优先级一般是一种错误。
范比例如以下:
  1. public class SimplePriorities implements Runnable{
  2. private int countDown = 5;
  3. private double d;
  4. private int priority;
  5. public SimplePriorities(int priority) {
  6. this.priority = priority;
  7. }
  8. public String toString( ) {
  9. return Thread.currentThread() + " : " + countDown;
  10. }
  11. public void run( ) {
  12. Thread.currentThread().setPriority( priority );
  13. while(true) {
  14. for(int i = 1 ; i < 100000 ; i++) {
  15. d += (Math.PI + Math.E) / (double)i;
  16. if(i % 1000 == 0)
  17. Thread.yield();
  18. }
  19. System.out.println(this);
  20. if( --countDown == 0) return;
  21. }
  22. }
  23. public static void main(String[] args) {
  24. ExecutorService exec = Executors.newCachedThreadPool();
  25. for(int i = 0; i < 5; i++)
  26. exec.execute( new SimplePriorities(Thread.MIN_PRIORITY));
  27. exec.execute( new SimplePriorities(Thread.MAX_PRIORITY));
  28. exec.shutdown();
  29. }
  30. }
/output:
Thread[pool-1-thread-6,10,main] : 5
Thread[pool-1-thread-4,1,main] : 5
Thread[pool-1-thread-6,10,main] : 4
Thread[pool-1-thread-6,10,main] : 3
Thread[pool-1-thread-6,10,main] : 2
Thread[pool-1-thread-3,1,main] : 5
Thread[pool-1-thread-4,1,main] : 4
Thread[pool-1-thread-2,1,main] : 5
..................................
    在上面的代码中使用了Thread.toString()方法来打印线程的名称、线程的优先级以及线程所属的"线程组"。你能够通过构造器来设置这个名称,这里是自己主动生成的名称。除此之外,通过Thread.currrentThread()能够获取当前线程的引用。
     虽然JDK有10个优先级,但它与多数操作系统都不能映射的非常好,由于每一个系统的优先级规则不同。唯一可移植的方法是当调整优先级的时候,仅仅使用MAX_PRIORITY、NORM_PRIORITY、和MIN_PRIORITY三种级别。

让步

     使用yield()方法能够给线程调度机制一个暗示:你的工作已经做得差点儿相同了,能够让别的线程使用CPU了(只是这仅仅是一个暗示,没有不论什么机制保证它将会被採纳)。当调用yield时,你也是在建议具有同样优先级的其它线程能够执行。
     使用Thread.yield()时常能够产生分布良好的处理机制。可是,大体上,对于不论什么重要的控制或在调整应用时,都不能依赖yield()。实际上,yield()常常被误用。

后台线程

     所谓后台(daemon)线程,是指在程序执行的时候在后台提供一种通用服务的线程,而且这样的线程并不属于程序中不可或缺的部分。因此,当全部的非后台线程结束时,程序也就终止了,同一时候会杀死进程中的全部后台线程。比方,执行main()的就是一个非后台线程。
  1. public class SimpleDaemons implements Runnable {
  2. public void run() {
  3. try{
  4. while(true) {
  5. TimeUnit.MILLISECONDS.sleep(100);
  6. System.out.println(Thread.currentThread()+" "+this);
  7. }
  8. } catch (InterruptedException e) {
  9. System.out.println("sleep() interrupted");
  10. }
  11. }
  12. public static void main(String []args) throws Exception {
  13. for(int i = 0; i < 10; i++) {
  14. Thread daemon = new Thread(new SimpleDaemons());
  15. daemon.setDaemon(true);
  16. daemon.start();
  17. }
  18. System.out.println("All daemons started");
  19. TimeUnit.MILLISECONDS.sleep( 175 );
  20. }
  21. }

/* Output: (Sample)
All daemons started
Thread[Thread-0,5,main] SimpleDaemons@41d931dc
Thread[Thread-7,5,main] SimpleDaemons@2ecb65c6
Thread[Thread-2,5,main] SimpleDaemons@567e5429
Thread[Thread-5,5,main] SimpleDaemons@7457599e
Thread[Thread-3,5,main] SimpleDaemons@656ba59f
Thread[Thread-6,5,main] SimpleDaemons@54fc93ce
Thread[Thread-9,5,main] SimpleDaemons@17b127fb
Thread[Thread-1,5,main] SimpleDaemons@fb14880
Thread[Thread-4,5,main] SimpleDaemons@481f35fd
Thread[Thread-8,5,main] SimpleDaemons@7670207

     必须在线程启动之前调用setDaemon()方法,才干将它设置为后台线程。
     假设我们要产生非常多的后台线程,能够使用ThreadFactory定制由Executor创建的线程的属性(后台、优先级、名称):
  1. public class DaemonThreadFactory implements ThreadFactory{
  2. public Thread newThread(Runnable r) {
  3. Thread t = new Thread(r);
  4. t.setDaemon(true);
  5. return t;
  6. }
  7. }
  8.  
  9. public class DaemonFromFactory implements Runnable {
  10. @Override
  11. public void run() {
  12. try{
  13. while(true) {
  14. TimeUnit.MILLISECONDS.sleep(100);
  15. System.out.println( Thread.currentThread() + " " +this);
  16. }
  17. } catch (InterruptedException e) {
  18. System.out.println("Interrupted");
  19. }
  20. }
  21. public static void main(String args[]) throws Exception {
  22. ExecutorService exec = Executors.newCachedThreadPool();
  23. for(int i = 0; i < 10; i++)
  24. exec.execute(new DaemonFromFactory());
  25. System.out.println("All daemons started");
  26. TimeUnit.MILLISECONDS.sleep(500);
  27. }
  28. }
     能够通过调用Thread对象的isDaemon()方法来确定线程是否是一个后台线程,假设是一个后台线程,那么它创建的不论什么线程都将被自己主动设置成后台线程。
     后台线程的finally子句可能得不到运行。当最后一个非后台线程终止时,后台线程会"突然"终止。因此,一旦main()退出,JVM就会马上关闭全部的后台进程,而不会有不论什么你希望出现的确认形式。

增加一个线程

     一个线程能够在其它线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续运行。假设某个线程在还有一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(t.isALive()返回为假)。
     也能够在调用join()时带上一个超时參数(单位能够是毫秒,或者纳秒),假设目标线程在这段时间到期时还没有结束的话,join()方法总能返回。

捕获异常

     因为线程的本质特性,使得你不能捕获从线程中逃逸的异常。一旦异常逃出任务的run()方法,它就会向外传播到控制台,除非你採取特殊的步骤捕获这样的错误的异常,使用Executor来解决问题。
     为了解决问题,我们要改动Executor产生线程的方式。Thread.UncaughtException-Handler是Jdk5.0的引入的接口,它同意你在每一个Thread对象上都附着一个异常处理器。Thread.UncaughtExceptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用。为了使用它,我们创建一个新类型的ThreadFactory,它将在每一个新创建的Thread对象上附着一个Thread.UncaughtExceptionHandler。我们将这个工厂传递给Executors创建新的ExecutorService的方法:
  1. class ExceptionThread2 implements Runnable {
  2. public void run() {
  3. Thread t = Thread.currentThread();
  4. System.out.println("run() by " + t);
  5. System.out.println("eh = " + t.getUncaughtExceptionHandler());
  6. throw new RuntimeException();
  7. }
  8. }
  9.  
  10. class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
  11. public void uncaughtException(Thread t, Throwable e) {
  12. System.out.println("caught " + e);
  13. }
  14. }
  15.  
  16. class HandlerThreadFactory implements ThreadFactory {
  17. public Thread newThread(Runnable r) {
  18. System.out.println(this + " creating new Thread");
  19. Thread t = new Thread(r);
  20. System.out.println("created " + t);
  21. t.setUncaughtExceptionHandler( new MyUncaughtExceptionHandler());
  22. System.out.println("eh = " + t.getUncaughtExceptionHandler());
  23. return t;
  24. }
  25. }
  26.  
  27. public class CaptureUncaughtException {
  28. public static void main(String []args) {
  29. ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory());
  30. exec.execute(new ExceptionThread2());
  31. }
  32. }

/* Output:
HandlerThreadFactory@4e25154f creating new Thread
created Thread[Thread-0,5,main]
eh = MyUncaughtExceptionHandler@70dea4e
run() by Thread[Thread-0,5,main]
eh = MyUncaughtExceptionHandler@70dea4e
caught java.lang.RuntimeException
     上面的演示样例使得你能够依照详细情况逐个地设置处理器。假设你知道要在代码中处处使用同样的异常处理器,那么更简单的方式是在Thread类中设置一个静态域,并将这个处理器设置为默认的未捕获异常处理器:
  1. public class SettingDefaultHandler {
  2. public static void main(String[] args) {
  3. Thread.setDefaultUncaughtExceptionHandler(
  4. new MyUncaughtExceptionHandler( ) );
  5. ExecutorService exec = Executors.newCachedThreadPool();
  6. exec.execute(new ExceptionThread());
  7. }
  8. }
     这个处理器仅仅有在不存在线程专有的未捕获异常处理器的情况下才会被调用。系统会检查线程的专有版本号,假设没有发现,则检查线程组是否有专有的uncaughtException()方法,假设也没有,再调用defaultUncaughtExceptionHandler。


漫谈并发编程(二):java线程的创建与基本控制的更多相关文章

  1. Java并发编程(01):线程的创建方式,状态周期管理

    本文源码:GitHub·点这里 || GitEE·点这里 一.并发编程简介 1.基础概念 程序 与计算机系统操作有关的计算机程序.规程.规则,以及可能有的文件.文档及数据. 进程 进程是计算机中的程序 ...

  2. Java并发编程:Java线程池

    转载自:http://www.cnblogs.com/dolphin0520/p/3932921.html 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题 ...

  3. Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析

    目录 引出线程池 Executor框架 ThreadPoolExecutor详解 构造函数 重要的变量 线程池执行流程 任务队列workQueue 任务拒绝策略 线程池的关闭 ThreadPoolEx ...

  4. Java并发编程:Java的四种线程池的使用,以及自定义线程工厂

    目录 引言 四种线程池 newCachedThreadPool:可缓存的线程池 newFixedThreadPool:定长线程池 newSingleThreadExecutor:单线程线程池 newS ...

  5. Java并发编程二三事

    Java并发编程二三事 转自我的Github 近日重新翻了一下<Java Concurrency in Practice>故以此文记之. 我觉得Java的并发可以从下面三个点去理解: * ...

  6. 并发编程——认识java里的线程

    本文系作者 chaoCode原创,转载请私信并在文章开头附带作者和原文地址链接. 违者,作者保留追究权利. 前言 并发编程在我们日常开发中是时时刻刻都有在用的,只不过大部分的代码底层已经帮我们去做了一 ...

  7. Java并发编程系列-(2) 线程的并发工具类

    2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务.可以将一个大任务,拆分成若干个小任务,如下图所示: Fork-Join框架利用了 ...

  8. Java线程:创建与启动

    Java线程:创建与启动 一.定义线程   1.扩展java.lang.Thread类.   此类中有个run()方法,应该注意其用法: public void run() 如果该线程是使用独立的 R ...

  9. 【并发编程】Java并发编程传送门

    本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. [并发编程系列博客传送门](https://www.cnblogs.com/54 ...

随机推荐

  1. 时间戳timestamp

    1 时间戳 数据库中自动生成的 唯一的 二进制的数据,通常用作给数据表的行添加版本戳的机制. timestamp与时间和日期无关. timestamp存储大小为8字节. 一个数据表只能有一个times ...

  2. 如何将自定义标签封装成一个Jar包

    当我们在一个web应用中开发好一些自定义标签的时候,这些自定义标签通常有标签处理器Java类,和一个描述这些标签tld文件,如果我们想在以后别的web工程中还能用上这些标签,可以将这些自定义标签封装在 ...

  3. TCP与UDP各自特点对比

    UDP和TCP是我们最常用的两种通信方式,下面就两者之间的特点做一个对比: 1.UDP主要用在实时性要求高以及对质量相对较弱的地方,如流媒体. 2.TCP既然是面向连接的,那么运行环境必然要求其保证可 ...

  4. Mongdb 访问

    http://114.55.75.xx/pics/201607040751367d21a38035bd4da7abd4473783f85f7a

  5. 关键部分CCriticalSection使用

    类CCriticalSection的对象表示一个“临界区”,它是一个用于同步的对象,同一时刻仅仅同意一个线程存取资源或代码区.临界区在控制一次仅仅有一个线程改动数据或其他的控制资源时很实用.比如,在链 ...

  6. Delphi中获取某类的祖先类及其所在单元名称(使用GetTypeData(PClass.ClassInfo)函数,并且该类是从TPersistent类的派生类才可以这么使用)

    前几天在CSDN社区看到一篇<如何得到自身单元名称>的帖子,其中一位名为sdzeng网友给出了答案.受此启发,自己写了一个函数,用来获取指定类的所有祖先类的名称及其所在的单元名称. //参 ...

  7. android使用篇(四) 注解依赖注入IOC实现绑定控件

    在android使用篇(三) MVC模式中提到一个问题: 1) 视图层(View):一般採用XML文件进行界面的描写叙述,使用的时候能够很方便的引入,可是用xml编写了,又须要在Acitvity声明而 ...

  8. 窗体透明,但窗体上的控件不透明(简单好用)good

    1.在Delphi中,设置窗体的AlphaBlend := true;AlphaBlendValue := 0-255; AlphaBlendValue越小窗体的透明度就越高.这种方法将会使窗体和窗体 ...

  9. Eclipse插件引入jar包的方法(转)

    搞了两天,终于找到解决办法了.原来  Eclipse 插件项目引入外面的jar包不能用   build path---->add external jars的方法. 先说明两个概念:类加载器,O ...

  10. 【LeetCode】Min Stack 解题报告

    [题目] Design a stack that supports push, pop, top, and retrieving the minimum element in constant tim ...