java线程和线程池的使用

java线程和线程池

一、创建多线程的方式

java多线程很常见,如何使用多线程,如何创建线程,java中有两种方式,第一种是让自己的类实现Runnable接口,第二种是让自己的类继承Thread类。其实Thread类自己也是实现了Runnable接口。具体使用实例如下:

1、通过实现Runnable接口方式


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

public class MyThread1 implements Runnable//通过实现Runnable接口方式

{

    String
sign = 
"thread#[email protected]";

    @Override

    public void run()

    {

        for (int i
0;
i < 
30;
i++)

        {

            System.out.println(sign
"->" +
i);

            try

            {

                Thread.sleep(100L);

            catch (InterruptedException
e)

            {

                e.printStackTrace();

            }

        }

    }

}

2、通过继承Thread类的方式


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

public class MyThread2 extends Thread//通过继承Thread类的方式

{

    String
sign = 
"thread#[email protected]";

    @Override

    public void run()

    {

        for (int i
0;
i < 
30;
i++)

        {

            System.out.println(sign
"->" +
i);

            try

            {

                Thread.sleep(100L);

            catch (InterruptedException
e)

            {

                e.printStackTrace();

            }

        }

    }

}

再启用上面创建的两种线程,调运代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

public static void main(String[]
args) 
throws InterruptedException

{

    long startTime
= System.currentTimeMillis();

    //
通过主线程启动自己的线程

    //
通过实现runnable接口

    Runnable
myThread1 = 
new MyThread1();

    Thread
thread1 = 
new Thread(myThread1);

    thread1.start();

    //
通过继承thread类

    Thread
thread2 = 
new Thread(new MyThread2());

    thread2.start();

    //
注意这里不是调运run()方法,而是调运线程类Thread的start方法,在Thread方法内部,会调运本地系统方法,最终会自动调运自己线程类的run方法

    //
让主线程睡眠

    Thread.sleep(1000L);

    System.out.println("主线程结束!用时:"

            +
(System.currentTimeMillis() - startTime));

    //
System.exit(0);

}

输入结果(每次输入可能不同)不再详细列出。对于上面的两种方式,更推荐使用实现Runnable接口的方式实现多线程。一方面是可以防止java单继承的顾虑,另一方面Runnable是面向接口编程,扩展性比起继承Thread更好。所以尽量使用implement Runnable的方式。

二、java线程类型说明

上面是将多线程跑起来了,但是有个问题,如果不让主线程睡眠,当主线程(比如main线程)结束以后,如果子线程还没结束,那么子线程是否还会执行呢?答案是会继续执行,为了说明这个问题,就又涉及到java中线程的类型。java中线程一共有两种类型:守护线程(daemon thread)和用户线程(user thread)又叫非守护线程。可以通过thread.setDaemon()方法设置线程是否为守护线程,默认是设置非守护线程(false)。java虚拟机停止运行的时间是虚拟机中运行的所有线程都是守护线程的时候,也就是说如果jvm中没有user
thread的时候,jvm就停止运行。或者说jvm在最后一个非守护线程结束的时候,将停止所有的守护进程,然后退出jvm。

当使用main方法开启线程时,主线程默认是非守护进程,而用户自己开的进程也是非守护进程。当主线程结束,但是子线程(默认是非守护线程)还没结束,所以虚拟机是不停止运行的,当子线程运行完以后,如果主线程也运行完毕,jvm发现没有非守护线程,就将jvm关闭,所以当main方法的主线程执行完毕以后,子线程是会继续执行的。当然我们也可以让在main主线程执行完毕以后,子线程不再执行,方法就是将所有的子线程设置为守护进程setDaemon(true)即可。但是需要注意的是这个设置需要在线程运行之前设置,不能在线程运行的过程中修改线程类型。

更直白点说如果用户将线程设置为守护进程,那实际的意思就是告诉jvm你不用搭理我这个线程,jvm想停止的时候,不用考虑我这个线程是否执行结束。这种线程具体的使用比如在垃圾回收机制的线程,就是一个守护线程。


1

2

3

4

5

6

7

8

9

10

11

12

13

Runnable
myThread1 = 
new MyThread1();

Thread
thread1 = 
new Thread(myThread1);

thread1.setDaemon(true);

thread1.start();

Thread
thread2 = 
new Thread(new MyThread2());

thread2.setDaemon(false);

thread2.start();

System.out.println("mainThread
isDaemon:"

        +
Thread.currentThread().isDaemon());

System.out.println("thread1
isDaemon:"
 +
thread1.isDaemon());

System.out.println("thread2
isDaemon:"
 +
thread2.isDaemon());

三、线程池的使用

上面已经看过了可以在代码中直接新起线程,如果我们在主线程中新起一百个线程,让这一百个线程同时工作,逻辑上是没有任何问题的,但是这样做对系统资源的开销很大,这样会在短时间内处理很多的任务,当然包括新起线程等等。基于这样的考虑,我们是有必要引入线程池这个东西的。线程池就是一个池子,池子里有很多可用线程资源,如果需要就直接从这个池子里拿就是。当不用的时候,放入池子中,线程池会自动帮我们管理。所以使用线程池主要有以下两个好处:1、减少在创建和销毁线程上所花的时间以及系统资源的开销 2、如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存 。

如果我们想要使用线程池,就需要先定义这个线程池。定义线程池的时候,其中的几个主要参数说明如下:

-corePoolSize(int):线程池中保持的线程数量,包括空闲线程在内。也就是线程池释放的最小线程数量界限。

-maximumPoolSize(int):线程池中嫩容纳最大线程数量。

-keepAliveTime(long):空闲线程保持在线程池中的时间,当线程池中线程数量大于corePoolSize的时候。

-unit(TimeUnit枚举类):上面参数时间的单位,可以是分钟,秒,毫秒等等。

-workQueue(BlockingQueue<Runnable>):任务队列,当线程任务提交到线程池以后,首先放入队列中,然后线程池按照该任务队列依次执行相应的任务。可以使用的workQueue有很多,比如:LinkedBlockingQueue等等。

-threadFactory(ThreadFactory类):新线程产生工厂类。

-handler(RejectedExecutionHandler类):当提交线程拒绝执行、异常的时候,处理异常的类。该类取值如下:(注意都是内部类)

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务,重复此过程。
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

除了自定义线程池以外, java提供了几种常用的线程池,可以快捷的供程序员使用,他们分别是:

1、newFixedThreadPool 创建固定大小数量线程池,数量通过传入的参数决定。

2、newSingleThreadExecutor 创建一个线程容量的线程池,所有的线程依次执行,相当于创建固定数量为1的线程池。

3、newCachedThreadPool 创建可缓存的线程池,没有最大线程限制(实际上是Integer.MAX_VALUE)。如果用空闲线程等待时间超过一分钟,就关闭该线程。

4、newScheduledThreadPool 创建计划(延迟)任务线程池,线程池中的线程可以让其在特定的延迟时间之后执行,也可以以固定的时间重复执行(周期性执行)。相当于以前的Timer类的使用。

5、newSingleThreadScheduledExecutor 创建单线程池延迟任务,创建一个线程容量的计划任务。

其实通过静态方法创建的上面几种线程池,也都是通过传入默认的各个参数,然后返回一个有各自特点的线程池。具体参数可以通过查看jdk源码阅读。

有了线程池,那么我们如何利用线程池中线程执行我们的任务,由于Java将线程池的封装,我们拿到的线程池的线程其实是一个包含线程任务的执行器,只需要调运执行器的执行方法,就会自动执行我们线程中的任务。对于非计划任务,我们需要拿到一个ThreadPoolExecutor,对于计划任务,我们需要拿到一个ScheduledThreadPoolExecutor(它是ThreadPoolExecutor的子类)。在了解这两个类之前,需要先了解两个接口,ExecutorService以及它的子接口ScheduleThreadExecutorService接口,上面两个接口分别实现了这两个接口,这个两接口定义了execute(Runnable
r)方法,这个方法去执行线程的任务。也就是我们通过调运ThreadPoolExecutor或者ScheduledThreadPoolExecutor的execute(Runnable r)方法开启我们的线程,并且执行我们的线程任务,具体代码如下:

定义一个单例的线程池:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

public class MyPool

{

    private static MyPool
myPool = 
null;

    //单例线程池中有两种具体的线程池

    private ThreadPoolExecutor
threadPool = 
null;

    private ScheduledThreadPoolExecutor
scheduledPool = 
null;

    public ThreadPoolExecutor
getThreadPool()

    {

        return threadPool;

    }

    public ScheduledThreadPoolExecutor
getScheduledPool()

    {

        return scheduledPool;

    }

    //设置线程池的各个参数的大小

    private int corePoolSize
10;//
池中所保存的线程数,包括空闲线程。

    private int maximumPoolSize
20;//
池中允许的最大线程数。

    private long keepAliveTime
3;//
当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

    private int scheduledPoolSize
10;

    private static synchronized void create()

    {

        if (myPool
== 
null)

            myPool
new MyPool();

    }

    public static MyPool
getInstance()

    {

        if (myPool
== 
null)

            create();

        return myPool;

    }

    private MyPool()

    {

        //实例化线程池,这里使用的LinkedBlockingQueue作为workQueue,使用DiscardOldestPolicy作为handler

        this.threadPool
new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,

                keepAliveTime,
TimeUnit.SECONDS,

                new LinkedBlockingQueue<Runnable>(),

                new ThreadPoolExecutor.DiscardOldestPolicy());

        //实例化计划任务线程池

        this.scheduledPool
new ScheduledThreadPoolExecutor(scheduledPoolSize);

    }

}

获取线程池中的线程,并且执行线程中任务:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

@Test

public void testThreadPool()

{

    ThreadPoolExecutor
pool1 = (ThreadPoolExecutor) Executors

            .newCachedThreadPool();

    pool1.execute(new Runnable()

    {

        @Override

        public void run()

        {

            System.out.println("快捷线程池中的线程!");

        }

    });

    ThreadPoolExecutor
pool2 = MyPool.getInstance().getThreadPool();

    pool2.execute(new Runnable()

    {

        @Override

        public void run()

        {

            System.out.println("普通线程池中的线程");

        }

    });

    ScheduledThreadPoolExecutor
pool3 = MyPool.getInstance()

            .getScheduledPool();

    pool3.scheduleAtFixedRate(new Runnable()

    {

        @Override

        public void run()

        {

            System.out.println("计划任务线程池中的线程");

        }

    }, 01000,
TimeUnit.MILLISECONDS);

}

四、计划任务执行使用

通过上面的例子,也看到了计划任务线程池的使用方式。对于计划任务,除了可以执行普不同线程池中线程的任务以外,还可以执行计划任务特殊的线程要求,比如:scheduleWithFixedDelay(command, initialDelay, delay, unit);在初始化延迟之后,以特定的延迟时间重复执行。scheduleAtFixedRate(command, initialDelay,
period, unit);在初始化延迟时间之后,以固定频率重复执行。这两种的区别是下一次执行时间延迟计算的开始时间不同,第一种是从上一次任务开始执行的时候计算,第二种是从上一次任务执行结束的时候计算。这两种和java之前版本中Timer类很相似。但是Timer有很多缺陷,具体的缺陷不再详细说明。而这些缺陷都可以通过ScheduledExecutorService给完美解决。所以是时候该丢弃Timer了。

时间: 2024-11-02 18:30:42

java线程和线程池的使用的相关文章

Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? Java new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start(); 1 2 3 4 5 6 7 new Thread(new

Java四种线程池

Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor 时间:2015-10-20 22:37:40      阅读:8762      评论:0      收藏:0      [点我收藏+] 介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行一个异

Java如何判断线程池所有任务是否执行完毕

package csdn;    import java.util.concurrent.ExecutorService;  import java.util.concurrent.Executors;    /**  * @author nineday  */  public class Test {        public static void main(String args[]) throws InterruptedException {          ExecutorServ

Java中的线程池

综述 在我们的开发中经常会使用到多线程.例如在Android中,由于主线程的诸多限制,像网络请求等一些耗时的操作我们必须在子线程中运行.我们往往会通过new Thread来开启一个子线程,待子线程操作完成以后通过Handler切换到主线程中运行.这么以来我们无法管理我们所创建的子线程,并且无限制的创建子线程,它们相互之间竞争,很有可能由于占用过多资源而导致死机或者OOM.所以在Java中为我们提供了线程池来管理我们所创建的线程. 线程池的使用 采用线程池的好处 在这里我们首先来说一下采用线程池的

java自带线程池和队列详细讲解(转)

Java线程池使用说明 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的.在jdk1.5之后这一情况有了很大的改观.Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用.为我们在开发中处理线程的问题提供了非常大的帮助. 二:线程池 线程池的作用: 线程池作用就是限制系统中执行线程的数量.     根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果:少了浪

java多线程、线程池的实现

Java实现多线程的3种方法:继承Thread类.实现runnable接口.使用ExecutorService,Callable.Future实现有返回值的多线程.前2种线程的实现方式没有返回值,第三种实现方式可以获取线程执行的返回值. 一:继承java.lang.Thread类 public class MyThread extends Thread { @Override public void run() { System.out.println( "my thread begin.&qu

java多线程之线程池

自JDK5之后,Java推出了一个并发包,java.util.concurrent,在Java开发中,我们接触到了好多池的技术,String类的对象池.Integer的共享池.连接数据库的连接池.Struts1.3的对象池等等,池的最终目的都是节约资源,以更小的开销做更多的事情,从而提高性能. 我们的web项目都是部署在服务器上,浏览器端的每一个request就是一个线程,那么服务器需要并发的处理多个请求,就需要线程池技术,下面来看一下Java并发包下如何创建线程池. 1.  创建一个可重用固定

深入浅出 Java Concurrency (33): 线程池 part 6 线程池的实现及原理 (1)[转]

线程池数据结构与线程构造方法 由于已经看到了ThreadPoolExecutor的源码,因此很容易就看到了ThreadPoolExecutor线程池的数据结构.图1描述了这种数据结构. 图1 ThreadPoolExecutor 数据结构 其实,即使没有上述图形描述ThreadPoolExecutor的数据结构,我们根据线程池的要求也很能够猜测出其数据结构出来. 线程池需要支持多个线程并发执行,因此有一个线程集合Collection<Thread>来执行线程任务: 涉及任务的异步执行,因此需要

深入浅出 Java Concurrency (36): 线程池 part 9 并发操作异常体系[转]

并发包引入的工具类很多方法都会抛出一定的异常,这些异常描述了任务在线程池中执行时发生的例外情况,而通常这些例外需要应用程序进行捕捉和处理. 例如在Future接口中有如下一个API: java.util.concurrent.Future.get(long, TimeUnit) throws InterruptedException, ExecutionException, TimeoutException; 在前面的章节中描述了Future类的具体实现原理.这里不再讨论,但是比较好奇的抛出的三

java 线程、线程池基本应用示例代码回顾

package org.rui.thread; /** * 定义任务 * * @author lenovo * */ public class LiftOff implements Runnable { protected int countDown=10; private static int taskCount=0; private final int id=taskCount++; public LiftOff(){} public LiftOff(int countDown) { thi