java 多线程及线程池总结


Java 多线程

Java 多线程的作用

由于串行化执行过程中,某些子过程由于IO性能瓶颈或者等待某些资源,会有等待空隙情况,多线程的执行,能够使得程序的整体更良好的使用CPU、内存等资源,提高整体的性能,既在同一时段,同时执行很多个任务。

 

Java 线程介绍

进程是程序或任务的执行过程,是资源和线程的拥有者,是一个动态过程,是一个执行过程;

线程:e.g. 当你打开qq的时候,qq 是一个进程,而你的文字交流和文件传输、视频窗口则是分别的线程,线程是最小的执行单元,共享进程的资源;

 

线程的常用方法:

线程的创建: Thread(),Thread(String name),Thread(Runnable tar),Thread(Runnable tar,String name)

线程的方法: start(),sleep(),join()

获取线程引用: Thread currentThread()

 

定义线程: extends Thread 或者 implements Runnable 两种方式实现

线程栈是指某时刻时内存中线程调度的栈信息,当前调用的方法总是位于栈顶。线程栈的内容是随着程序的运行动态变化的,因此研究线程栈必须选择一个运行的时刻(实际上指代码运行到什么地方)。

 

线程的状态及线程之间的状态转换:新建、可运行、运行、阻塞\等待\睡眠、死亡;

 

线程方法:

 

1.start()方法。

  一个线程对象生成后,如果要产生一个执行的线程,就一定要调用它的start()方法.在介绍这个方法时不得不同时说明run方法.其实线程对象的run方法完全是一个接口回调方法,它是你这个线程对象要完成的具体逻辑.简单说你要做什么就你在run中完成,而如何做,什么时候做就不需要你控制 了,你只要调用start()方法,JVM就会管理这个线程对象让它产生一个线程并注册到线程处理系统中。

从表面上看,start()方法调用了run()方法,事实上,start()方法并没有直接调用run方法.JDK1.5以前 start()方法是本地方法,它如何最终调用run方法已经不是JAVA程序员所能了解的.而在JDK1.5,原来的那个本地start()方法被 start0()代替

从中我们可以看到这个为了控制一个线程对象只能运行成功一次start()方法.这是因为线程的运行要获取当前环境,包括安全,父线程的权限优先级等条件,如果一个线程对象可以运行多次,那么定义一个static 的线程在一个环境中获取相应权限和优先级,运行完成后它在另一个环境中利用原来的权限和优先级等属性在当前环境中运行,这样就造成无法预知的结果.简单说 来,让一个线程对象只能成功运行一次,是基于对线程管理的需要。

start()方法最本质的功能是从CPU中申请另一个线程空间来执行 run()方法中的代码,它和当前的线程是两条线,在相对独立的线程空间运行,也就是说,如果你直接调用线程对象的run()方法,当然也会执行,但那是 在当前线程中执行,run()方法执行完成后继续执行下面的代码.而调用start()方法后,run()方法的代码会和当前线程并发(CPU)或并行 (CPU)执行。

  所以请记住一句话调用线程对象的run方法不会产生一个新的线程,虽然可以达到相同的执行结果,但执行过程和执行效率不同。

  线程的interrupt()方法,interrupted()isInterrupted()

  这三个方法是关系非常密切而且又比较复杂的,虽然它们各自的功能很清楚,但它们之间的关系有大多数人不是真正的了解。

  先说interrupt()方法,它是实例方法,而它也是最奇怪的方法,java语言中,线程最初被设计为"隐晦难懂"的东西,直到现在它的 语义不没有象它的名字那样准确。大多数人以为,一个线程象调用了interrupt()方法,那它对应的线程就应该被中断而抛出异常,事实中,当一个线程 对象调用interrupt()方法,它对应的线程并没有被中断,只是改变了它的中断状态。

  使当前线程的状态变以中断状态,如果没有其它影响,线程还会自己继续执行。

  只有当线程执行到sleep,wait,join等方法时,或者自己检查中断状态而抛出异常的情况下,线程才会抛出异常。

如果线程对象调用interrupt()后它对应的线程就立即中断,那么interrupted()方法就不可能执行。

   因为interrupted()方法是一个static方法,就是说只能在当前线程上调用,而如果一个线程interrupt()后它已经中断了,那它又如何让自己interrupted()?

  正因为一个线程调用interrupt()后只是改变了中断状态,它可以继续执行下去,在没有调用sleep,wait,join等法或自己抛 出异常之前,它就可以调用interrupted()来清除中断状态(还会原状)interrupted()方法会检查当前线程的中断状态,如果为 "被中断状态"则改变当前线程为"非中断状态"并返回true,如果为"非中断状态"则返回false,它不仅检查当前线程是否为中断状态,而且在保证当 前线程回来非中断状态,所以它叫"interrupted",是说中断的状态已经结束(到非中断状态了)isInterrupted()方法则仅仅检查线 程对象对应的线程是否是中断状态,并不改变它的状态。

  目前大家只能先记住这三个方法的功能,只有真正深入到多线程编程实践中,才会体会到它们为什么是对象方法,为什么是类方法。

  线程到底什么时候才被中断抛出InterruptedException异常,我们将在提高篇中详细讨论。

  [sleep(),join(),yield()方法]

  在现在的环节中,我只能先说明这些方法的作用和调用原则,至于为什么,在基础篇中无法深入,只能在提高篇中详细说明。

  sleep()方法中是类方法,也就是对当前线程而言的,程序员不能指定某个线程去sleep,只能是当前线程执行到sleep()方法时,睡 眠指定的时间(让其它线程运行).事实上也只能是类方法,在当前线程上调用.试想如果你调用一个线程对象的sleep()方法,那么这个对象对应的线程如 果不是正在运行,它如何sleep()?所以只有当前线程,因为它正在执行,你才能保证它可以调用sleep()方法。

  原则:[在同步方法中尽量不要调用线程的sleep()方法],或者简单说,对于一般水平的程序员你基本不应该调用sleep()方法。

  join()方法,正如第一节所言,在一个线程对象上调用join方法,是当前线程等待这个线程对象对应的线程结束,比如有两个工作,工作A要耗时10秒钟,工作B要耗时10秒或更多。我们在程序中先生成一个线程去做工作B,然后做工作A

  

  原则:[join是测试其它工作状态的唯一正确方法],我见过很多人,甚至有的是博士生,在处理一项工作时如果另一项工作没有完成,说让当前工 作线程sleep(x),我问他,你这个x是如何指定的,你怎么知道是100毫秒而不是99毫秒或是101毫秒?其实这就是OnXXX事件的实质,我们不 是要等多长时间才去做什么事,而是当等待的工作正好完成的时候去做。

  yield()方法也是类方法,只在当前线程上调用,理由同上,它主是让当前线程放弃本次分配到的时间片原则:[不是非常必要的情况下,没有理 由调用它].调用这个方法不会提高任何效率,只是降低了CPU的总周期上面介绍的线程一些方法,基于(基础篇)而言只能简单提及.以后具体应用中我会结合 实例详细论述。

  线程本身的其它方法请参看API文档.下一节介绍非线程的方法,但和线程密切相关的两[]个对象方法:

  [wait(),notify()/notifyAll()]

  这是在多线程中非常重要的方法。

 

关于这两个方法,有很多的内容需要说明.在下面的说明中可能会有很多地方不能一下子明白,但在看完本节后,即使不能完全明白,你也一定要回过头来记住下面的两句话:

  [wait(),notify()/notityAll()方法是普通对象的方法(Object超类中实现),而不是线程对象的方法]

  [wait(),notify()/notityAll()方法只能在同步方法中调用]

 

 

 

Java 多线程的难点--同步

由于java多线程在同一时段执行多个线程,并且线程之间可能会共享某些资源,可能将导致不同线程访问同一资源时出现错误访等问题,java的线程同步就是力图解决多线程任务在执行的时候出现错误访问等情况。

线程锁: 多线程的同步是通过锁实现的,java中每个对象都有一个内置锁;当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例有关的锁当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。

 

关于锁和同步:

1)、只能同步方法,而不能同步变量和类;

2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?

3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。

4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法(如果分别是不同实例调用,那么则并行不悖了),那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。

6)、线程睡眠时,它所持的任何锁都不会释放

7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。

8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。

9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:

    public int fix(int y) {
        synchronized (this) {
            x = x - y;
        }
        return x;
    }

 

 

要同步静态方法,需要一个用于整个类对象的锁,这个对象是就是这个类(XXX.class)

 

 

Java 线程池的使用

线程池的意义:

复用:类似WEB服务器等系统,长期来看内部需要使用大量的线程处理请求,而单次请求响应时间通常比较短,此时Java基于操作系统的本地调用方式大量的创建和销毁线程本身会成为系统的一个性能瓶颈和资源浪费。若使用线程池技术可以实现工作线程的复用,即一个工作线程创建和销毁的生命周期期间内可以执行处理多个任务,从而总体上降低线程创建和销毁的频率和时间,提升了系统性能。

流控:服务器资源有限,超过服务器性能的过高并发设置反而成为系统的负担,造成CPU大量耗费于上下文切换、内存溢出等后果。通过线程池技术可以控制系统最大并发数和最大处理任务量,从而很好的实现流控,保证系统不至于崩溃。

功能:JDK的线程池实现的非常灵活,并提供了很多功能,一些场景基于功能的角度会选择使用线程池。

线程池的重要组成部分:

· 工作者线程worker:即线程池中可以重复利用起来执行任务的线程,一个worker的生命周期内会不停的处理多个业务job。线程池复用的本质就是复用一个worker去处理多个job流控的本质就是通过对worker数量的控制实现并发数的控制。通过设置不同的参数来控制worker的数量可以实现线程池的容量伸缩从而实现复杂的业务需求

· 待处理工作job的存储队列:工作者线程workers的数量是有限的,同一时间最多只能处理最多workers数量个job。对于来不及处理的job需要保存到等待队列里,空闲的工作者work会不停的读取空闲队列里的job进行处理。基于不同的队列实现,可以扩展出多种功能的线程池,如定制队列出队顺序实现带处理优先级的线程池、定制队列为阻塞有界队列实现可阻塞能力的线程池等。流控一方面通过控制worker数控制并发数和处理能力,一方面可基于队列控制线程池处理能力的上限。

· 线程池初始化:即线程池参数的设定和多个工作者workers的初始化。通常有一开始就初始化指定数量的workers或者有请求时逐步初始化工作者两种方式。前者线程池启动初期响应会比较快但造成了空载时的少量性能浪费,后者是基于请求量灵活扩容但牺牲了线程池启动初期性能达不到最优。

· 处理业务job算法:业务给线程池添加任务job时线程池的处理算法。有的线程池基于算法识别直接处理job还是增加工作者数处理job或者放入待处理队列,也有的线程池会直接将job放入待处理队列,等待工作者worker去取出执行。

· workers的增减算法:业务线程数不是持久不变的,有高低峰期。线程池要有自己的算法根据业务请求频率高低调节自身工作者workers的数量来调节线程池大小,从而实现业务高峰期增加工作者数量提高响应速度,而业务低峰期减少工作者数来节省服务器资源。增加算法通常基于几个维度进行:待处理工作job数、线程池定义的最大最小工作者数、工作者闲置时间。

线程池终止逻辑:应用停止时线程池要有自身的停止逻辑,保证所有job都得到执行或者抛弃。

 

 

为什么要用线程池:

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

比较重要的几个类:

ExecutorService

真正的线程池接口。

ScheduledExecutorService

能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。

ThreadPoolExecutor

ExecutorService的默认实现。

ScheduledThreadPoolExecutor

继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

 

1. newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

2. newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

3. newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

4. ScheduledThreadPoolExecutor

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

 

ScheduledThreadPoolExecutor 和Timer区别:   

ScheduledThreadPoolExecutor exec= new ScheduledThreadPoolExecutor(1);

 

Timer:   TimeTask task=new TimeTask();  new Timer(task);

区别

Timer对调度的支持是基于绝对时间的,因此任务对系统时间的改变是敏感的;而ScheduledThreadPoolExecutor支持相对时间。
Timer使用单线程方式来执行所有的TimerTask,如果某个TimerTask很耗时则会影响到其他TimerTask的执行;
而ScheduledThreadPoolExecutor则可以构造一个固定大小的线程池来执行任务。
Timer 不会捕获由TimerTask抛出的未检查异常,故当有异常抛出时,Timer会终止,导致未执行完的TimerTask不再执行,新的 TimerTask也不能被调度;
ScheduledThreadPoolExecutor对这个问题进行了妥善的处理,不会影响其他任务的执行。

Java 线程误区:http://blog.csdn.net/overmaker/article/details/2096802

 

 

1.http://www.cnblogs.com/yshb/archive/2012/06/15/2550367.html

2.http://lavasoft.blog.51cto.com/62575/99150

3. http://blog.csdn.net/zuoluoboy/article/details/4034935

4. http://programming.iteye.com/blog/158568【】

 


Java多线程编程中,配置线程池的参数是非常重要的。根据不同的业务需求,可以设置线程池的参数来控制线程的数量和行为。根据引用中的例子,可以使用`Executors.newFixedThreadPool(int nThreads)`来创建一个固定线程数量的线程池。在这个方法中,`nThreads`参数表示线程池中的线程数量,只有这个数量的线程会被创建。然后,可以使用`pool.execute(Runnable command)`方法来提交任务给线程池执行。 在配置线程池时,需要考虑业务的性质。如果是CPU密集型的任务,比如加密、计算hash等,最佳线程数一般为CPU核心数的1-2倍。而如果是IO密集型的任务,比如读写数据库、文件、网络读写等,最佳线程数一般会大于CPU核心数很多倍。这样可以充分利用IO等待时间来执行其他任务,提高程序的性能。引用中给出了一些常见的线程池特点和构造方法参数。 总之,根据业务需求和特点,合理配置线程池的参数可以提高程序的性能和效率。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java多线程线程池的参数和配置](https://blog.csdn.net/MRZHQ/article/details/129107342)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Java多线程线程池(合理分配资源)](https://blog.csdn.net/m0_52861000/article/details/126869155)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Java多线程线程池](https://blog.csdn.net/weixin_53611788/article/details/129602719)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值