Java线程池原理及实现(详解)

线程的高级应用

 

文档版本

V1.0

说明:线程池原理介绍。

线程池

线程池是一种多线程处理形式,处理过程中将任务加入到队列,然后在创建线程后自己主动启动这些任务。线程池线程都是后台线程。每一个线程都使用默认的堆栈大小,以默认的优先级执行。并处于多线程单元中。

假设某个线程在托管代码中空暇(如正在等待某个事件),则线程池将插入还有一个辅助线程来使全部处理器保持繁忙。

假设全部线程池线程都始终保持繁忙,但队列中包括挂起的工作,则线程池将在一段时间后创建还有一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程能够排队,但他们要等到其它线程完毕后才启动。

组成部分

1、线程池管理器(ThreadPoolManager):用于创建并管理线程池

2、工作线程(WorkThread): 线程池中线程

3、任务接口(Task):每一个任务必须实现的接口。以供工作线程调度任务的运行。

4、任务队列:用于存放没有处理的任务。提供一种缓冲机制。

 

技术背景

在面向对象编程中,创建和销毁对象是非常费时间的,由于创建一个对象要获取内存资源或者其他很多其他资源。在Java中更是如此,虚拟机将试图跟踪每个对象。以便可以在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能降低创建和销毁对象的次数,特别是一些非常耗资源的对象创建和销毁。

怎样利用已有对象来服务就是一个须要解决的关键问题,事实上这就是一些"池化资源"技术产生的原因。比方大家所熟悉的数据库连接池正是遵循这一思想而产生的。

功能

应用程序能够有多个线程。这些线程在休眠状态中须要耗费大量时间来等待事件发生。

其它线程可能进入睡眠状态,而且仅定期被唤醒以轮循更改或更新状态信息,然后再次进入休眠状态。为了简化对这些线程的管理,.NETJAVA为每一个进程提供了一个线程池,一个线程池有若干个等待操作状态。当一个等待操作完毕时,线程池中的辅助线程会运行回调函数。线程池中的线程由系统管理。程序员不须要费力于线程管理,能够集中精力处理应用程序任务。

应用范围

1、需要大量的线程来完成任务。且完成任务的时间比较短。

网页(http)请求这种任务,使用线程池技术是很合适的。由于单个任务小,而任务数量巨大,你能够想象一个热门站点的点击次数。 但对于长时间的任务,比方一个Telnet连接请求,线程池的长处就不明显了。由于Telnet会话时间比线程的创建时间大多了。

2、对性能要求苛刻的应用,比方要求server迅速响应客户请求。

3、接受突发性的大量请求,但不至于使server因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,尽管理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现"OutOfMemory"的错误。

Java线程池原理及实现

线程简介

 创建线程有两种方式:继承Thread或实现Runnable。

Thread实现了Runnable接口。提供了一个空的run()方法。所以不论是继承Thread还是实现Runnable,都要有自己的run()方法。


    一个线程创建后就存在。调用start()方法就开始执行(执行run()方法)。调用wait进入等待或调用sleep进入休眠期,顺利执行完成或休眠被中断或执行过程中出现异常而退出。

wait和sleep比较:
      sleep方法有:sleep(long millis),sleep(long millis, long nanos),调用sleep方法后,当前线程进入休眠期,暂停运行,但该线程继续拥有监视资源的全部权。到达休眠时间后线程将继续运行,直到完毕。若在休眠期还有一线程中断该线程。则该线程退出。
      wait方法有:wait(),wait(long timeout),wait(long timeout, long nanos)。调用wait方法后,该线程放弃监视资源的全部权进入等待状态;
      wait():等待有其他的线程调用notify()或notifyAll()进入调度状态。与其他线程共同争夺监视。wait()相当于wait(0),wait(0, 0)。
      wait(long timeout):当其他线程调用notify()或notifyAll()。或时间到达timeout亳秒,或有其他某线程中断该线程。则该线程进入调度状态。
      wait(long timeout, long nanos):相当于wait(1000000*timeout + nanos)。仅仅只是时间单位为纳秒。

线程池

多线程技术主要解决处理器单元内多个线程运行的问题。它能够显著降低处理器单元的闲置时间。添加处理器单元的吞吐能力。
    
   
如果一个server完毕一项任务所需时间为:T1 创建线程时间,T2 在线程中运行任务的时间,T3 销毁线程时间。


    
   
假设:T1 + T3 远大于 T2,则能够採用线程池。以提高server性能。
                一个线程池包含下面四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池。包含 创建线程池,销毁线程池,加入新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态。能够循环的运行任务;
3、任务接口(Task):每一个任务必须实现的接口,以供工作线程调度任务的运行。它主要规定了任务的入口。任务运行完后的收尾工作,任务的运行状态等。
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

线程池技术正是关注怎样缩短或调整T1,T3时间的技术。从而提高server程序性能的。它T1T3分别安排在server程序的启动和结束的时间段或者一些空暇的时间段,这样在server程序处理客户请求时,不会有T1T3的开销了

    线程池不仅调整T1,T3产生的时间段。并且它还显著降低了创建线程的数目,看一个样例:

    如果一个server一天要处理50000个请求,而且每一个请求须要一个单独的线程完毕。在线程池中,线程数通常是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果server不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的server程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。

 

同步与异步

他们最大的区别:同步需要等待,而异步无需等待。

 

例子:

普通B/S模式(同步)AJAX技术(异步)  

同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干别的事。

异步: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕。

说的再通俗易懂点

同步是按顺序执行,执行完一个再执行下一个,需要等待、协调运行。

异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。

线程就是异步实现的一个方式。

异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。

 

异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。

异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。

实现异步可以采用多线程技术或则交给另外的进程来处理。

 

详解:

同步和异步区别:(好处和坏处)

 

同步可以避免出现死锁,读脏数据的发生。

一般共享某一资源的时候用,如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错,同步就会按顺序来修改。

但是,同步需要等待资源访问结束,浪费时间,效率低。 

 

异步则可以提高效率,

现在cpu都是双核,四核,异步处理的话可以同时做多项工作,当然必须保证是可以并发处理的。

但是安全性较低。

 

 

拓展:

并发:

操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥

互斥:

进程间相互排斥的使用临界资源的现象,就叫互斥。

并行:

在单处理器中多道程序设计系统中,进程被交替执行,表现出一种并发的外部特征;在多处理器系统中,进程不仅可以交替执行,而且可以重叠执行。在多处理器上的程序才可实现并行处理。从而可知,并行是针对多处理器而言的。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生。  

多线程:

多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行。

 

实现线程同步的几种方式

1.同步方法 
即有synchronized关键字修饰的方法。 
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 
内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

代码如:

public synchronized void save(){}

注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

 

2.同步代码块 
即有synchronized关键字修饰的语句块。 
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

 

代码如:

synchronized(object){

}

注:同步是一种高开销的操作,因此应该尽量减少同步的内容。

通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

 

3.使用特殊域变量(volatile)实现线程同步 
a.volatile关键字为域变量的访问提供了一种免锁机制, 
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新, 
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

 

例如: 
在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。

class Bank {

            //需要同步的变量加上volatile

            private volatile int account = 100;

 

            public int getAccount() {

                return account;

            }

            //这里不再需要synchronized

            public void save(int money) {

                account += money;

            }

       

多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。

 

4.使用重入锁实现线程同步 
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 
ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 
它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。

ReenreantLock类的常用方法有:
    ReentrantLock() : 创建一个ReentrantLock实例 
    lock() : 获得锁 
unlock() : 释放锁 

class Bank {

 

            private int account = 100;

            //需要声明这个锁

            private Lock lock = new ReentrantLock();

            public int getAccount() {

                return account;

            }

            //这里不再需要synchronized

            public void save(int money) {

                lock.lock();

                try{

                    account += money;

                }finally{

                    lock.unlock();

                }

 

            }

        
注:关于Lock对象和synchronized关键字的选择: 
    a.最好两个都不用,使用一种java.util.concurrent包提供的机制, 
        能够帮助用户处理所有与锁相关的代码。 
    b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码
    c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 

5.使用局部变量实现线程同步 
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 
副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

 

ThreadLocal 类的常用方法
ThreadLocal() : 创建一个线程本地变量 
get() : 返回此线程局部变量的当前线程副本中的值 
initialValue() : 返回此线程局部变量的当前线程的"初始值" 
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
 
例如: 
    在上面例子基础上,修改后的代码为: 
代码实例: 

public class Bank{

            //使用ThreadLocal类管理共享变量account

            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){

                @Override

                protected Integer initialValue(){

                    return 100;

                }

            };

            public void save(int money){

                account.set(account.get()+money);

            }

            public int getAccount(){

                return account.get();

            }

        }

 

注:ThreadLocal与同步机制 
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 
b.前者采用以”空间换时间”的方法,后者采用以”时间换空间”的方式。

 

阻塞与非阻塞

名词解释

Telnet

 

Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用者的电脑上使用telnet程序,用它连接到服务器终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个telnet会话,必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。

 

telnet请求主要用于远程登录操作。

远程登录的工作过程如下:

使用Telnet协议进行远程登录时需要满足以下条件:在本地计算机上必须装有包含Telnet协议的客户程序;必须知道远程主机的Ip地址或域名;必须知道登录标识与口令。

Telnet远程登录服务分为以下4个过程:

1)本地与远程主机建立连接。该过程实际上是建立一个TCP连接,用户必须知道远程主机的Ip地址或域名

2)将本地终端上输入的用户名和口令及以后输入的任何命令或字符以NVT(Net Virtual Terminal)格式传送到远程主机。该过程实际上是从本地主机向远程主机发送一个IP数据包;

3)将远程主机输出的NVT格式的数据转化为本地所接受的格式送回本地终端,包括输入命令回显和命令执行结果;

4)最后,本地终端对远程主机进行撤消连接。该过程是撤销一个TCP连接。

 

TCP/IP

Tcp/ip是Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。Tcpip协议可以保证数据传输到目标地址。

 

IP

IP层接收由更低层(网络接口层例如以太网设备驱动程序)发来的数据包,并把该数据包发送到更高层---TCP或UDP层;相反,IP层也把从TCP或UDP层接收来的数据包传送到更低层。IP数据包是不可靠的,因为IP并没有做任何事情来确认数据包是否按顺序发送的或者有没有被破坏,IP数据包中含有发送它的主机的地址(源地址)和接收它的主机的地址(目的地址)。

高层的TCP和UDP服务在接收数据包时,通常假设包中的源地址是有效的。也可以这样说,IP地址形成了许多服务的认证基础,这些服务相信数据包是从一个有效的主机发送来的。IP确认包含一个选项,叫作IP source routing,可以用来指定一条源地址和目的地址之间的直接路径。对于一些TCP和UDP的服务来说,使用了该选项的IP包好像是从路径上的最后一个系统传递过来的,而不是来自于它的真实地点。这个选项是为了测试而存在的,说明了它可以被用来欺骗系统来进行平常是被禁止的连接。那么,许多依靠IP源地址做确认的服务将产生问题并且会被非法入侵。

TCP

TCP是面向连接的通信协议,通过三次握手建立连接,通讯完成时要拆除连接,由于TCP是面向连接的所以只能用于端到端的通讯。

TCP提供的是一种可靠的数据流服务,采用“带重传的肯定确认”技术来实现传输的可靠性。TCP还采用一种称为“滑动窗口”的方式进行流量控制,所谓窗口实际表示接收能力,用以限制发送方的发送速度。

如果IP数据包中有已经封好的TCP数据包,那么IP将把它们向‘上’传送到TCP层。TCP将包排序并进行错误检查,同时实现虚电路间的连接。TCP数据包中包括序号和确认,所以未按照顺序收到的包可以被排序,而损坏的包可以被重传。

TCP将它的信息送到更高层的应用程序,例如Telnet的服务程序和客户程序。应用程序轮流将信息送回TCP层,TCP层便将它们向下传送到IP层,设备驱动程序和物理介质,最后到接收方。

面向连接的服务(例如TelnetFTPrloginX WindowsSMTP)需要高度的可靠性,所以它们使用了TCP。DNS在某些情况下使用TCP(发送和接收域名数据库),但使用UDP传送有关单个主机的信息。

三次握手

三次握手(three times handshake;three-way handshake)所谓的“三次握手”即对每次发送的数据量是怎样跟踪进行协商使数据段的发送和接收同步,根据所接收到的数据量而确定的数据确认数及数据发送、接收完毕后何时撤消联系,并建立虚连接。

为了提供可靠的传送,TCP在发送新的数据之前,以特定的顺序将数据包的序号,并需要这些包传送给目标机之后的确认消息。TCP总是用来发送大批量的数据。当应用程序在收到数据后要做出确认时也要用到TCP。

握手过程

第一次

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次

第二次握手服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_40001362/article/details/81911281