Java网络编程 线程

线程作用

当服务器需要大量并发连接的时候,一般使用轻量级的线程来处理大量的连接,而不是重量级的进程。线程在资源使用上更宽松,因为它们会共享内存。使用线程来代替进程,可以再让你的服务器性能提升三倍。再结合使用可重用的线程池,在同样的硬件和网络连接下,服务器的运行可以快9倍多!采用多线程设计设计会更容易,可以将程序分解为多个线程,分别执行独立的操作。

由于现代虚拟机和操作系统中线程可以提供很高的性能,而且构建基于线程的服务器相对简单,所以开始时总会考虑采用基于线程的设计。如果确实遇到麻烦,应该考虑将应用分解到多个冗余的服务器上,而不要全倚仗一个服务器上的3倍性能提升。

运行线程

这里有两种方法运行线程:

  1. 对Thread类派生子类,覆盖其run()方法。
  2. 实现Runnable接口,写run方法,将Runnable对象传递给Thread构造函数。

不论采用哪种方法,关键都在于run()方法:

    public void run()

你应当把线程要做的工作都放在这个方法中,线程要在这里启动,并在这里结束。

  1. 多线程程序会在main()方法以及所有非守护线程(nondaemon thread)都返回时才退出(非守护线程是指后台线程,完成后台任务)。
  2. Runnable接口不一定绝对优于继承Thread类。有些情况下在Thread类的子类构造函数调用Thread类的一些方法可能很有用。而在继承其他类的时候,就需要使用RUnnable接口。
  3. 有些崇尚面向对象的人认为,线程完成的任务实际上不是一种Thread,因此应当放在一个单独的类或接口(如Runnable),而不应该放在Thread的子类。作者部分同意这种观点,但不认为这个观点像其声称的那样理由充分。
  4. 尽量不要在构造函数在启动线程,否则可能会发生竞态条件。可能会在构造函数结束而且对象完全初始化之前允许新线程进行回调。

回调

一种简单有效的方法是让线程告诉主程序它何时结束,这是通过调用主类(即启动这个线程的类)中的一个方法来做到的,这被称为回调。这样一来,主程序就可以在等待线程结束期间休息,而不会占用运行线程的时间。

这里有两种普通做法:

  1. 调用主类的静态方法。
  2. 使用主类的实例方法。

使用实例方法要复杂一些,但有很多优点。它可以做到每个线程跟踪一个主类对象,不需要额外的数据结构。

同步

因为不同线程共享相同的内存,一个线程完全有可能会破坏另一个线程使用的变量的数据结构,这时候就需要同步技术来解决问题了。

同步块

把一个对象锁包围在synchronized块中,它会对这个对象进行同步。同步要求在同一个对象上同步的所有代码要连续地运行,而不能并行运行。

示例:

synchronized(System.out){
    //同步连续过程
}

同步方法

由于用对象本身来同步整个方法体是很常见的,所以Java为此提供了一个快捷方式。对当前对象(this引用)同步整个方法。

public synchronized void fun() {
    //同步连续过程
}

但是同步方法并不是万能的:

  1. 同步方法会使得VM性能严重下降(不过最新的VM已经大为改进)。
  2. 大大增加了死锁的可能。
  3. 不总是对象本身需要防止同时修改或访问,需要注意哪个才是关键要上锁的对象。

同步的替代方式

对于线程调度引起的行为不一致,但其实同步并不总是这个问题最好解决方案。

  • 使用局部变量而不是字段。局部变量不存在同步问题。
  • 基本类型的方法也可以在单独的线程中安全地修改,因为Java按值而不是按引用来传递基本类型对象。
  • 不可变对象永远也不会改变状态。
  • 构造函数一般不需要担心线程安全问题。在构造函数返回之前,没有线程有这个对象的引用,所以不可能有两个线程都有这个对象的引用。(但不等于构造函数启动线程,不会发生竞态条件。)
  • 在类中利用不变性,将其所有字段声明为private和final,而且不要编写任何可能改变它们的方法。
  • 将非线程安全的类用作线程安全的类的一个私有字段。只要包含类只以线程安全的方式访问这个非安全类,而且永远不让这个私有字段的引用泄露到另一个对象中,那么这个类就是安全的。
  • 对于基本数据类型,使用java.util.concurrent.atomic包中特意设计为保证线程安全但可变的类。不过需要说明的是,这不会让对象本身也是线程安全的,只是该引用变量的获取和设置是线程安全的。
  • 对于映射和列表等集合,使用java.util.Collections的方法把它们包装在一个线程安全的版本中。

不过这些只是单个的原子方法调用,如果需要作为一个原子连续地完成两个操作以上,中间不能有中断,就需要同步。

死锁

也有可能发生这样的情况,两个线程太过小心,每个线程都在等待对方的资源的独占访问权,却永远得不到,这会导致死锁。

  • 同步应当式确保线程安全的最后一道防线。如果确实需要同步,要保持同步块尽可能小,而且尽量不要一次同步多个对象。
  • 如果多个对象需要操作相同的共享资源集,要确保以相同的顺序请求这些资源。

线程调度

优先级

在Java中,10是最高优先级,0是最低优先级。

UNIX相反,0是最高级,10是最低级。而Windows只有7个优先级,1、2、3以及8、9会分别分配到两个相同的优先级。

线程的优先级可以使用setPriority()来改变。

抢占

每个虚拟机都有一个线程调度器,确定在给定时刻运行哪个线程。主要有两种线程调度:抢占式(preemptive)协作式(cooperative)

下面是不同的方式可以让线程暂停或准备暂停:

阻塞

任何时候线程必须停下来等待它没有的资源时,就会发生阻塞。即使阻塞几毫秒,这一点时间也足够其他线程用来完成重要的任务。

放弃

显式地放弃可以通过调用Thread.yield()静态方法来做到。

放弃并不会释放这个线程拥有的锁。因此,在理想情况下,在线程放弃时不应该做任何同步。

休眠

休眠是更有力的放弃方式。不管有没有其他线程准备运行,休眠线程都会暂停。这样一来,不只是其他有相同优先级的线程得到机会,还会给较低优先级的线程一个运行的机会。

通过调用两个重载的Thread.sleep()静态方法之一,线程可以进入休眠。

想唤醒休眠的线程,可以通过休眠线程的interrupt()方法来实现,会让休眠中的线程得到一个InterruptedException异常。

连接线程

Java提供三个join()方法,允许一个线程在继续执行之前等待另一个线程结束。同样,连接的另一个的线程的线程可以被中断,可以调用通过该线程的interrupt()方法来实现,会得到一个InterruptedException异常。

如今,连接线程可能没有Java5之前那么重要。具体来讲,很多原来需要join()的设计现在用Executor和Future更容易地实现。

等待一个对象

线程可以等待一个它锁定的对象。希望暂停的线程首先必须使用syschronized获得这个锁的对象,然后调用这个对象的三个重载wait()方法之一。

线程会保持休眠,直到发生以下三种情况之一

  • 超时。不过,如果线程不能立即重新获得所等待的对象的锁,它可能仍要阻塞一段时间。
  • 中断。调用interrupt()方法。不过,在抛出异常前线程要重新获得所等待对象的锁,所以调用后,该线程可能仍要阻塞一段时间。
  • 通知。在这个线程所等待的对象山调用notify()notifyAll(),就会发生通知。notify()基本随机地从等待这个对象的线程列表中选择一个线程,并将它唤醒。notifyAll()方法会唤醒等待指定对象的每一个线程。如果成功,继续执行。如果失败,会对这个对象阻塞,直到可以得到锁,然后继续执行。

    不要假定因为线程得到了通知,对象现在就处于正确的状态。要保证对象进入正确的状态之前,再也不会进入不正确的状态,如果无法保证这一点,就要显式地进行检查,一般要将wait ()调用放在检查当前状态对象的循环中。

结束

run()方法返回时,线程将撤销,其他线程可以接管CPU。

线程池

如果并发线程达到4000至20000时,大多数Java虚拟机可能会由于内存耗尽而无法承受。不过,通过使用线程池而不是为每个连接生成新进程,服务器每分钟就可以用不到100个线程来处理数千个短连接。

使用Excutor类,只需要将各个任务作为一个Runnable对象提交给这个线程池,你就会得到一个Future对象,可以用来检查任务的进度。

  • 调用Executors.ewFixedThreadPool()可以创建一个固定数目的线程池。
  • 调用submit()用来提交线程任务。
  • 调用shutdown(),不会终止等待的工作,只是通知线程池没有更多的任务需要增加到它的内部队列。而且一旦完成所有等待的工作,就应当关闭。
  • 调用shutdownNow(),终止当前处理中的任务,并忽略所有等待的任务。

Excutor与回调

Java5引用了一个多线程编程的新方法,可以更容易处理回调。

不再是直接创建一个线程,你要创建一个ExcutorService,它会根据你需要的固定线程数为你创建线程(其实是创建一个线程池了),可以向ExecutorService提交Callable任务,每个Callable任务分别得到一个Future。之后可以向Future请求得到任务的结果。如果任务已经准备就绪,就会立即得到这个结果。如果还没有准备好,轮训线程会阻塞,直到结果准备就绪,再返回这个结果。

示例:

    ExecutorService service = Executors.newFixedThreadPool(2)。

    Future<Integer> future1 = service.submit(task1);
    Future<Integer> future2 = service.submit(task2);

    return Math.max(future1.get(), future2.get());
时间: 2024-10-15 09:18:18

Java网络编程 线程的相关文章

Java网络编程基础(六)— 基于TCP的NIO简单聊天系统

在Java网络编程基础(四)中提到了基于Socket的TCP/IP简单聊天系统实现了一个多客户端之间护法消息的简单聊天系统.其服务端采用了多线程来处理多个客户端的消息发送,并转发给目的用户.但是由于它是基于Socket的,因此是阻塞的. 本节我们将通过SocketChannel和ServerSocketChannel来实现同样的功能. 1.客户端输入消息的格式 username:msg    username表示要发送的的用户名,msg为发送内容,以冒号分割 2.实现思路 实现思路与Java网络

java网络编程serversocket

转载:http://www.blogjava.net/landon/archive/2013/07/24/401911.html Java网络编程精解笔记3:ServerSocket详解ServerSocket用法详解 1.C/S模式中,Server需要创建特定端口的ServerSocket.->其负责接收client连接请求. 2.线程池->包括一个工作队列和若干工作线程->工作线程不断的从工作队列中取出任务并执行.-->java.util.concurrent->线程池

java网络编程socket\server\TCP笔记(转)

java网络编程socket\server\TCP笔记(转) 2012-12-14 08:30:04|  分类: Socket |  标签:java  |举报|字号 订阅 1 TCP的开销 a  连接协商三次握手,c->syn->s,s->syn ack->c, c->ack->s b  关闭协商四次握手,c->fin->s, s->ack-c,s->fin->c,c->ack->s c  保持数据有序,响应确认等计算开销 d

Java网络编程基础【转】

网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编程是比较复杂的系统工程,需要了解很多和网络相关的基础知识,其实这些都不是很必需的.首先来问一个问题:你 会打手机吗?很多人可能说肯定会啊,不就是按按电话号码,拨打电话嘛,很简单的事情啊!其实初学者如果入门网络编程的话也可以做到这么简单! 网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据.

第13篇-JAVA 网络编程

第13篇-JAVA 网络编程 每篇一句 比我强大的人都在努力我还有什么理由不拼命 初学心得 不去追逐你所渴求你将永远不会拥有 (笔者JEEP/711)[JAVA笔记 | 时间:2017-04-22| JAVA 网络编程 ] 1.网络编程基本概念 1.什么是计算机网络 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备通过通信线路连接起来在网络操作系统网络管理软件及网络通讯协议的管理和协调下实现资源共享和信息传递的计算机系统 把分布在不同地理区域的计算机与专门的外部设备用通讯线路互

Java网络编程(一)

Java网络编程: 1.1: 网络编程:对于我这个“研究不深”的网络菜鸟来说,我觉得网络编程就是实现计算机与计算机之间通信的编程.写些能够实现计算机与计算机之间的通信就行了(目前来说). 1.2:一台计算机跟另外计算机通讯. 计算机与计算机通讯的三大要素: 1:ip地址---电脑 1.1:作用:唯一标识一台计算机. 回环地址:127.0.0.1==主机:localhost 主机地址作用:测试网卡是否正常. 2:找到相应的应用程序----端口号 端口号-----具有网络功能的应用程序的标识号,没有

Java学习笔记—第十二章 Java网络编程入门

第十二章  Java网络编程入门 Java提供的三大类网络功能: (1)URL和URLConnection:三大类中最高级的一种,通过URL网络资源表达方式,可以很容易确定网络上数据的位置.利用URL的表示和建立,Java程序可以直接读入网络上所放的数据,或把自己的数据传送到网络的另一端. (2)Socket:又称"套接字",用于描述IP地址和端口(在Internet中,网络中的每台主机都有一个唯一的IP地址,而每台主机又通过提供多个不同端口来提供多种服务).在客户/服务器网络中,当客

【Java】Java网络编程菜鸟进阶:TCP和套接字入门

Java网络编程菜鸟进阶:TCP和套接字入门 JDK 提供了对 TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用户数据报协议)这两个数据传输协议的支持.本文开始探讨 TCP. TCP 基础知识 在“服务器-客户端”这种架构中,服务器和客户端各自维护一个端点,两个端点需要通过网络进行数据交换.TCP 为这种需求提供了一种可靠的流式连接,流式的意思是传出和收到的数据都是连续的字节,没有对数据量进行大小限制.

java 网络编程复习(转)

好久没有看过Java网络编程了,现在刚好公司有机会接触,顺便的拾起以前的东西 参照原博客:http://www.cnblogs.com/linzheng/archive/2011/01/23/1942328.html 一.网络编程  通过使用套接字来达到进程间通信目的的编程就是网络编程. 二.网络编程中常见的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定I