java线程是否可以重用,是否一个线程执行完了就销毁了?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java线程是否可以重用,是否一个线程执行完了就销毁了?相关的知识,希望对你有一定的参考价值。

有个需求,有N个设备,我想给每个设备分配一个SOCKET线程(不是实例)每一秒读取一次硬件信息,我想把这N个线程放在MAP里,用设备ID作KEY,每次用MAP.GET(ID)的方式取到线程,如果不存在就NEW一个新线程出来放进MAP里,请问这种方式是否可行,会不会出现数据错乱的现象,比如说设备一个信息跑到设备二上去了?
或者是有什么好的方法来实现,多线程不太懂,谢谢各位

这个方法当然不可行。我给你举个例子,你去火车站买票。窗口就这么多,有人来了就排队,不会每个人都有一个窗口。如果人实在太多了,可以考虑增设一些窗口,人少了,又可以关闭一些窗口。线程可以重复使用,不必执行完一个线程就销毁了。售票员卖了一张票还要继续卖票,等到下班了,(这一天)他便不卖了。一样的道理,等某个时间你确定不会在有任务要到来,那么你可以销毁这些线程了。你可以熟悉下,线程池技术。这个正是你所需要的追问

调用代码
t = threadMap.get(eid)
if(t == null)
t = new Thread(new Handler(socketMap));
threadMap.put(eid,t)
t.start();

线程代码:
while(true)
socket = socketMap.get(eid)
if(socket==null || socket.isClosed())
soket = new Socket(ip,port);

soket.doRequest()
Thread.sleep(1000);

合理吗?有什么问题?
请问有没有好的关于SOCKET线程池的资料,推荐一下,谢谢!

追答

不是每个设备都需要一个,如果N很大的话,很浪费资源。我这没有资料,大概的思路是这样的:上来创建几个线程,然后每个线程有一个任务队列,新来的任务添加到任务队尾,执行一个任务,任务就出列;当队列为空的时候,线程等待。

参考技术A mt2.start(); mt1休眠时,mt2还是不执行。程序停了。等到mt1休眠之后再设置mt2优先级高于mt1,然后让2个线程同时运行,然后再让mt1挂起。 public 参考技术B 默认你创建一个新线程。执行完里面的代码后就会消失,这也是我们很多时候用线程处理任务的时候用到一个循环, 参考技术C 把待处理的对象放到一个阻塞队列里。往队列里填东西时 用new Thread() run()queue.put(),
在处理它的线程run中, 还是那个queue, queue.take();
put和take是一对可阻塞线程的方法。 BlockingQueue在初始化时是需要定个大小的,就是说这个队列最多有多少个东西。put在入里填东西时,如果queue满了,它会阻塞,直到有地方可填。所以你不能直接put,要new一个动态的线程出来,。 那处理它的方法中从里面往出take,如果没有东西可take,它也会阻塞,直到有东西它就往下执行。这样你就不用怕while(true)了,因为没东西可执行时,线程会在take处阻塞线程继续执行。

虽然过一年半了,但是我百度东西时摆到这儿了,就试着也答答。

Java多线程系列:Java线程池的使用方式,及核心运行原理

之前谈过多线程相关的4种常用Java线程锁的特点,性能比较、使用场景,今天主要分享线程池相关的内容,这些都是属于Java面试的必考点。

为什么需要线程池

java中为了提高并发度,可以使用多线程共同执行,但是如果有大量线程短时间之内被创建和销毁,会占用大量的系统时间,影响系统效率。

为了解决上面的问题,java中引入了线程池,可以使创建好的线程在指定的时间内由系统统一管理,而不是在执行时创建,执行后就销毁,从而避免了频繁创建、销毁线程带来的系统开销。


线程池如何使用,以及实现原理,处理步骤,有什么使用注意事项等,今天主要从这几个方面详细介绍Java线程池。


技术图片



线程池的处理流程

就以ThreadPoolExecutor为例,当我们把一个Runnable交给线程池去执行的时候,这个线程池处理的流程是这样的:

技术图片
  1. 先判断线程池中的核心线程们是否空闲,如果空闲,就把这个新的任务指派给某一个空闲线程去执行。如果没有空闲,并且当前线程池中的核心线程数还小于 corePoolSize,那就再创建一个核心线程。
  2. 如果线程池的线程数已经达到核心线程数,并且这些线程都繁忙,就把这个新来的任务放到等待队列中去。如果等待队列又满了,那么查看一下当前线程数是否到达maximumPoolSize,如果还未到达,就继续创建线程。
  3. 如果已经到达了,就交给RejectedExecutionHandler(拒绝策略)来决定怎么处理这个任务。

线程池的使用(ThreadPoolExecutor)

在Java中,线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类,是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。

ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器

public class ThreadPoolExecutor extends AbstractExecutorService {

…..

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue workQueue);

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue workQueue,ThreadFactory threadFactory);

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue workQueue,RejectedExecutionHandler handler);

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

}

ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。


下面解释下一下构造器中各个参数的含义:


1.corePoolSize(线程池的基本大小)

当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。


2.runnableTaskQueue(任务队列)

用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
  • PriorityBlockingQueue:一个具有优先级得无限阻塞队列。

3.maximumPoolSize(线程池最大大小)

线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。


4.ThreadFactory:用于设置创建线程的工厂

可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常又帮助。


5.RejectedExecutionHandler(饱和策略)

当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。n AbortPolicy:直接抛出异常。

  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。
  • 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

6.keepAliveTime(线程活动保持时间)

线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。


7.TimeUnit(线程活动保持时间的单位)

可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

线程池的注意事项

虽然线程池能大大提高服务器的并发性能,但使用它也会存在一定风险。与所有多线程应用程序一样,用线程池构建的应用程序容易产生各种并发问题,如对共享资源的竞争和死锁。此外,如果线程池本身的实现不健壮,或者没有合理地使用线程池,还容易导致与线程池有关的死锁、系统资源不足和线程泄漏等问题。


1) 建议使用new ThreadPoolExecutor(…)的方式创建线程池

线程池的创建不应使用
Executors 去创建,而应该通过 ThreadPoolExecutor
创建,这样可以让读者更加明确地知道线程池的参数设置、运行规则,规避资源耗尽的风险,这一点在也阿里巴巴JAVA开发手册中也有明确要求。这一点不容小觑,曾有同学因为线程池使用不当导致生产的同一台机器上部署的多个应用都因无法创建线程池而出现故障。


2) 合理设置线程数

线程池的工作线程数设置应根据实际情况配置,CPU密集型业务(搜索、排序等)CPU空闲时间较少,线程数不能设置太多。

如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1

如果是IO密集型任务,参考值可以设置为2*NCPU


3) 设置能代表具体业务的线程名称

这样方便通过日志的线程名称识别所属业务。具体实现可以通过指定ThreadPoolExecutor的ThreadFactory参数,如使Spring提供的CustomizableThreadFactory。



没钱没人脉也能轻松入门,让你每年多赚10万!

技术图片




















以上是关于java线程是否可以重用,是否一个线程执行完了就销毁了?的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程系列:Java线程池的使用方式,及核心运行原理

Java多线程系列:Java线程池的使用方式,及核心运行原理

JAVA 线程池原理

java 多线程分段等待执行完成状况,循环屏障CyclicBarrier | Java工具类

Java中是否父线程阻塞后子线程就无法继续执行?

java 优雅的实现多线程等待,可重用的同步屏障Phaser | Java工具类