标签:
当服务器需要大量并发连接的时候,一般使用轻量级的线程来处理大量的连接,而不是重量级的进程。线程在资源使用上更宽松,因为它们会共享内存。使用线程来代替进程,可以再让你的服务器性能提升三倍。再结合使用可重用的线程池,在同样的硬件和网络连接下,服务器的运行可以快9倍多!采用多线程设计设计会更容易,可以将程序分解为多个线程,分别执行独立的操作。
由于现代虚拟机和操作系统中线程可以提供很高的性能,而且构建基于线程的服务器相对简单,所以开始时总会考虑采用基于线程的设计。如果确实遇到麻烦,应该考虑将应用分解到多个冗余的服务器上,而不要全倚仗一个服务器上的3倍性能提升。
这里有两种方法运行线程:
run()
方法。run
方法,将Runnable对象传递给Thread构造函数。不论采用哪种方法,关键都在于run()
方法:
public void run()
你应当把线程要做的工作都放在这个方法中,线程要在这里启动,并在这里结束。
- 多线程程序会在
main()
方法以及所有非守护线程(nondaemon thread)都返回时才退出(非守护线程是指后台线程,完成后台任务)。- Runnable接口不一定绝对优于继承Thread类。有些情况下在Thread类的子类构造函数调用Thread类的一些方法可能很有用。而在继承其他类的时候,就需要使用RUnnable接口。
- 有些崇尚面向对象的人认为,线程完成的任务实际上不是一种Thread,因此应当放在一个单独的类或接口(如Runnable),而不应该放在Thread的子类。作者部分同意这种观点,但不认为这个观点像其声称的那样理由充分。
- 尽量不要在构造函数在启动线程,否则可能会发生竞态条件。可能会在构造函数结束而且对象完全初始化之前允许新线程进行回调。
一种简单有效的方法是让线程告诉主程序它何时结束,这是通过调用主类(即启动这个线程的类)中的一个方法来做到的,这被称为回调。这样一来,主程序就可以在等待线程结束期间休息,而不会占用运行线程的时间。
这里有两种普通做法:
使用实例方法要复杂一些,但有很多优点。它可以做到每个线程跟踪一个主类对象,不需要额外的数据结构。
因为不同线程共享相同的内存,一个线程完全有可能会破坏另一个线程使用的变量的数据结构,这时候就需要同步技术来解决问题了。
把一个对象锁包围在synchronized块中,它会对这个对象进行同步。同步要求在同一个对象上同步的所有代码要连续地运行,而不能并行运行。
示例:
synchronized(System.out){
//同步连续过程
}
由于用对象本身来同步整个方法体是很常见的,所以Java为此提供了一个快捷方式。对当前对象(this引用)同步整个方法。
public synchronized void fun() {
//同步连续过程
}
但是同步方法并不是万能的:
对于线程调度引起的行为不一致,但其实同步并不总是这个问题最好解决方案。
不过这些只是单个的原子方法调用,如果需要作为一个原子连续地完成两个操作以上,中间不能有中断,就需要同步。
也有可能发生这样的情况,两个线程太过小心,每个线程都在等待对方的资源的独占访问权,却永远得不到,这会导致死锁。
在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()
,终止当前处理中的任务,并忽略所有等待的任务。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());
标签:
原文地址:http://blog.csdn.net/sinat_24229853/article/details/51980118