J2EE课程设计《项目开发指导》——第9章 编程开发多线程安全的项目代码(第1部分)
杨教授工作室 精心创作的优秀程序员 职业提升必读系列资料
第9章 编程开发多线程安全的项目代码(第1/4部分)
并发访问是企业应用系统的基本要求,在Java语言中提供对线程及多线程技术的内置支持,使得开发人员能够在应用系统的开发过程中实现并发访问的功能。另外,Java语言还提供有synchronized关键字和wait/notify方法等方式以解决在并发访问中的共享资源冲突和避免线程死锁的问题。
当然,采用synchronized关键字所产生的同步互斥代码块会给应用系统带来阻塞和死锁,这不仅会影响应用系统的整体访问的性能,也在一定的程度上降低了应用系统的可靠性。为此,在Java语言中又提供了线程局部变量ThreadLocal类为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本产生冲突。
编程开发多线程安全的项目代码是提高企业应用系统可靠性的基本保障,但什么是多线程安全和不安全的代码,如何能够开发出多线程安全的项目代码,如何正确地应用Java语言中的各种形式的集合,作者在本章中试图为读者解答这些技术问题以提高课程设计中的代码质量和所开发出的应用系统项目的可靠性。
1.1 编程开发多线程安全的项目代码
1.1.1 9.1.1 多线程技术及多线程的并发问题
1、多线程技术的主要优点
Java在语言级别就提供有对多线程技术的内部支持,开发人员在项目开发中充分地应用多线程技术,能够达到以下的开发效果:
, 可以减轻应用系统性能方面的瓶颈
因为多线程技术可以使得项目中的程序代码并行地执行和操作,从而能够在一定的程度上提高系统程序的执行速度——比如,目前比较流行的利用多线程技术实现程序下载。
, 还能够提高CPU处理器的效率
因为在多线程技术中,Java 虚拟机JVM通过优先级的管理机制,可以使重要的线程程序代码优先执行。这一方面可以提高项目中任务管理的灵活性;另一方面,在多CPU的计算机系统中,开发人员可以把不同的线程分配在不同的CPU中执行,真正做到同时处理多任务的并发执行的效果。
杨教授工作室,版权所有,盗版必究, 1/15页
杨教授工作室 精心创作的优秀程序员 职业提升必读系列资料
2、正确地应用多线程技术和灵活地控制它并不是一件简单的事情
在JDK API的java.lang核心包中为开发人员提供有Thread类和Runnable接口以支持单线程的编程实现。但在项目开发中,正确地应用多线程技术和灵活地控制它其实也不是一件简单的事情——首先由多线程所带来的性能改善是以应用系统本身的可靠性为代价的,其次不正确的多线程程序代码还有可能会产生出线程死锁等方面的问题。主要是因为:
1) 一方面,创建出多个线程也是会消耗系统资源的;
2) 另一方面,对多个线程的管理控制也相当困难——这涉及多线程之间的通信、同步
互斥和同步协调执行等方面的技术问题如何正确地解决,
3) 更重要的是,如何对线程间共享资源进行合理处理以完全避免死锁的产生,
因此,当允许多个线程能够同时访问某个共享对象中的属性和方法时,对这些调用的程序代码进行同步处理是非常重要的。否则,一个线程程序代码可能会中断另一个线程正在执行的任务或者改变另一个线程对共享对象中的属性修改,使该共享对象处于一种无效的访问状态、并影响到应用系统的业务逻辑和业务
流程
快递问题件怎么处理流程河南自建厂房流程下载关于规范招聘需求审批流程制作流程表下载邮件下载流程设计
的正确完成。
开发人员此时可以利用JProfiler监控工具程序监测系统中的各个线程的运行状态、并及时地发现出存在死锁的线程——请见下图9.1所示JProfiler监控工具程序对线程监测结果的局部截图。
图9.1 JProfiler监控工具程序对线程监测结果的局部截图
3、Java语言中提供有监视器技术以解决多线程的并发访问问题
不正确的多线程编程实现代码会导致在并发访问时产生冲突、阻塞和死锁等方面的技术问题,因此在Java语言中也提供了对多线程并发控制的有效技术手段——监视器(或者称为同步锁)。
线程监视器为开发人员提供了一种同步锁定的机制——当某个线程进入到线程监视器
杨教授工作室,版权所有,盗版必究, 2/15页
杨教授工作室 精心创作的优秀程序员 职业提升必读系列资料
并对某个共享资源访问后,其它的线程则不能再进入到线程监视器中,直到被同步锁定的线程退出该线程监视器后,下一个等待执行的线程才能进入到监视器中并被执行、然后才允许它对共享资源访问。
线程监视器实现同步锁定的基本原理类似于读者在现实生活中在共用电话亭内打电话,当某人进入到电话亭内打电话时,该人便关闭电话亭的门而使得其他的人无法再进入到电话亭内;只有电话亭内的人打完电话并走出电话亭后,下一个人才能够进入电话亭。 4、正确地应用监视器的同步锁定机制解决多线程的并发访问问题
(1)如何产生出线程监视器
在Java语言的多线程开发中,线程监视器是通过同步互斥代码块来体现的——同步互斥代码块可以是整个类、类中的某些成员方法、乃至某段代码块等形式。而只需要对某段代码块采用synchronized关键字加以定义就可以形成为同步互斥代码块。 (2)各种形式的同步互斥代码编程定义格式
, 同步互斥方法的示例:public synchronized void myFun(){
//允许访问控制的代码
}
, 同步互斥代码块的示例:synchronized(syncObject){
//允许访问控制的代码
}
由于synchronized 关键字标识的代码块可以针对任意的代码块,且可任意指定上锁的对象,因此在应用时的灵活性是比较高的。当然,开发人员也应该正确地编程实现同步互斥代码:应该要将访问共享数据的代码模块设计为synchronized关键字标识的同步互斥代码块;并且synchronized关键字可以用来限定一个方法或一段语句块乃至整个类——此时该类中的所有public形式的成员方法都是synchronized的同步方法,下面为一个采用synchronized关键字标识的同步类的示例代码片段:
public synchronized class SomeOneClass{
//类中的各个成员方法和成员属性
}
(3)同步锁定的实现机制
每一个拥有由synchronized关键字标识的代码块的对象都含有一个独立的线程监视器,
杨教授工作室,版权所有,盗版必究, 3/15页
杨教授工作室 精心创作的优秀程序员 职业提升必读系列资料
只有某一个线程的synchronized关键字标识的代码块执行完后其它线程的synchronized关键字标识的代码块才能被执行——从而达到对不同的线程程序相互隔离的目标,但这样的结果将会导致应用系统的总体运行的效率会降低。
5、多线程之间的死锁问题
(1)什么是多线程之间的死锁问题
当两个或两个以上的线程同时执行时,如果每个线程都占有一个共享的系统资源并还要请求另一个共享的系统资源,这时就会出现死锁的可能性——也就是两个对象都在调用对方的同步代码、都在等着对方释放同步锁;或者如果一个线程已经持有一个同步锁并还试图再获取同步锁时,就会出现死锁的危险。读者在课程设计的项目开发中应该要尽可能避免出现这样的状况。
(2)为什么会产生多线程之间的死锁问题
导致死锁的根源是在于开发人员不适当地运用synchronized关键词来管理线程对特定对象的访问而造成的。因为synchronized关键词的主要作用是确保在某个时刻只有一个线程被允许执行特定的代码块,但当线程访问某个“同步方法”时,Java虚拟机JVM会给该对象加同步锁,而这个同步锁导致其它也想访问同一对象的其它线程将被阻塞,直至第一个线程释放它加在对象上的同步锁为止,此时将产生出彼此相互等待的状况——也就是死锁的现象。
(3)在多线程的开发实现中应该尽可能避免出现死锁
多线程的死锁问题或其它多线程方面的错误可能只在某些特殊的应用场合下才会反映出错误现象——具有一定的随机性;同时在不同的Java 虚拟机JVM中运行时,其错误的表现也可能是不同的——这给错误的定位和排除带来了一定的复杂性。
此时可以利用JProfiler监控工具程序监测系统中的各个线程是否出现死锁的状况——请见下图9.2所示JProfiler监控工具程序对银行账户信息管理系统中的各个线程监测结果的局部截图——系统没有出现多线程的死锁问题。
杨教授工作室,版权所有,盗版必究, 4/15页
杨教授工作室 精心创作的优秀程序员 职业提升必读系列资料
图9.2 利用JProfiler监控工具程序对某系统中的各个线程监测结果的局部截图 1.1.2 什么是多线程安全和不安全的代码
1、多线程程序访问时的共享资源
共享资源一般是指基于多线程的应用开发中每个线程都要访问的同一类的对象实例变量——static类型或者非static类型,它可以是单个类变量或类中的成员对象实例变量,也可以是一组类变量或实例变量(如集合对象等)。
在多线程编程开发中,如果某个功能实现需要涉及使用共享资源时,必须要保证共享资源在任何时候被访问时都应该是原子的、一致性的状态,而达到这个目标的线程程序就是线程安全的线程程序。
当然,如果一个对象的完整生命周期都处在同一个线程内——例如程序中的局部变量,开发人员此时不需要考虑对该局部变量的线程安全处理。
2、线程安全和线程不安全的功能实现代码
(1)线程安全的功能实现代码
如果某段功能实现代码是可重入的(ReEentrant)或者通过某种形式的同步互斥技术手段而实现对并发访问的共享资源的保护——此种代码可以被认为是线程安全的代码。 (2)局部变量和全局变量在多线程访问下的差别
各种程序语言中的局部变量由于是处在程序内的局部作用域内,其完整生命周期都处在同一个线程内,因此对局部变量的使用是线程安全的;而全局变量由于涉及到多个不同的代码块的共享访问,而这些程序代码块如果是被多个不同的线程程序访问,而线程是有可能处在并发执行的状态,因此全局变量在多线程的读写访问状况下则有可能是不安全的。
因此,开发人员有必要充分地应用面向对象编程技术中的“封装”机制,对共享数据的访问需要实施一定的隔离和保护措施。
(3)一个线程不安全的访问代码示例说明
下面【例9-1】中的示例程序中有一个线程产生0-49共50个数,并且这50个数保存在Vector集合中,而由另一个线程输出保存在Vector集合中的各个数据元素。
【例9-1】所示的VectorSynThread类代码通过实现Runnable接口而自身为一个线程,同时在其startAllThread方法中再构造出另外两个线程,并且同时启动这两个线程。在VectorSynThread类的run方法中识别当前正在执行的线程的名称而分别获得和存储Vector集合中的数据——该Vector集合此时为VectorSynThread线程类的共享资源。
杨教授工作室,版权所有,盗版必究, 5/15页
杨教授工作室 精心创作的优秀程序员 职业提升必读系列资料
【例9-1】线程不安全的程序访问代码示例
package com.px1987.thread;
import java.util.Vector;
public class VectorSynThread implements Runnable{
private Vector oneVector=null;
public VectorSynThread(){
oneVector=new Vector();
}
public void storeDataToVector(){
for(int index=0;index<50;index++){
oneVector.addElement(new Integer(index));
System.out.println(Thread.currentThread().getName()+": "+index);
}
}
public void getDataFromVector(){
for(int index=0;index
规范
编程规范下载gsp规范下载钢格栅规范下载警徽规范下载建设厅规范下载
中各种内置的对象变量——如JSP页面中的out、request、response和
session、config、page以及pageContext等都是线程安全的,而由于application对象
的作用域在整个Web应用系统内都能够被访问,所以对它的访问也是线程不安全
的。
3) JSP页面中的某个方法的局部变量——局部变量由于是在堆栈中分配的,每个线程
都有它自己的堆栈空间,所以对它的访问是线程安全的。
4) 静态类中的对象数据——静态类程序本身不需要具体被对象实例化,不同的线程都
可以直接对它内部的各种对象数据访问操作和使用。因此,对这些对象数据的访问
也是线程不安全的。
(3)线程不安全的JSP页面代码示例
在下面的【例9-4】的示例JSP页面中定义了两个实例变量userName和onePrintWriter、和一个实例方法showSomeUserName。由于实例变量userName和onePrintWriter有可能被多个不同的线程共享访问——这些线程分别处理不同客户端程序的请求,并且在该JSP页面代码中也没有采取什么方面的同步保护的措施。
杨教授工作室,版权所有,盗版必究, 14/15页
杨教授工作室 精心创作的优秀程序员 职业提升必读系列资料
因此,该JSP页面中的代码同样也存在着线程不安全性的问题——也将出现与【例9-3】
示例中类似的问题。
【例9-4】线程不安全的JSP页面代码示例
<%@ page contentType="text/html;charset=gb2312"%>
线程不安全的JSP页面示例
<%!
String userName=null;
java.io.PrintWriter onePrintWriter;
public void showSomeUserName() {
try{
Thread.sleep(5000);
}
catch (InterruptedException e){
}
onePrintWriter.println("请求的用户名称为:"+userName );
}
%>
<%
userName = request.getParameter("userName");
onePrintWriter = response.getWriter();
showSomeUserName();
%>
杨教授工作室,版权所有,盗版必究, 15/15页