少年阿宾

那些青春的岁月

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  500 Posts :: 0 Stories :: 135 Comments :: 0 Trackbacks

#

     摘要: 前言:本文指在介绍Spring框架中的JdbcTemplate类的使用方法,涉及基本的Spring反转控制的使用方法和JDBC的基本概念。目标是使读者能够对JdbcTemplate快速地掌握和使用。        准备:1. Spring的基本概念      ...  阅读全文
posted @ 2012-06-25 10:28 abin 阅读(629) | 评论 (0)编辑 收藏

MyBatis中,可以使用Generator自动生成代码,包括DAO层、 MODEL层 、MAPPING SQL映射文件。 

第一步:下载MyBatis的Generator工具 
下载地址:http://code.google.com/p/mybatis/downloads/detail?name=mybatis-generator-core-1.3.1-bundle.zip&can=3&q=Product%3DGenerator 

第二步:配置自动生成代码所需的XML配置文件,例如(generator.xml)  
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- classPathEntry:数据库的JDBC驱动,换成你自己的驱动位置 -->
<classPathEntry location="D:\libs\ojdbc14.jar" />
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 去除自动生成的注释 -->
<commentGenerator>
<property name="suppressAllComments" value="true" />
</commentGenerator>
<jdbcConnection driverClass="oracle.jdbc.driver.OracleDriver" connectionURL="jdbc:oracle:thin:@172.16.88.10:1521:mydb" userId="abc" password="abc">
</jdbcConnection>
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:自动生成代码的位置 -->
<javaModelGenerator targetPackage="com.test.model" targetProject="E:\eclipse 3.5.2\workspace\gao\src">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<sqlMapGenerator targetPackage="com.test.mapping"  targetProject="E:\eclipse 3.5.2\workspace\gao\src">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.test.dao"  targetProject="E:\eclipse 3.5.2\workspace\gao\src">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- tableName:用于自动生成代码的数据库表;domainObjectName:对应于数据库表的javaBean类名 -->
<table tableName="pds_system_item" domainObjectName="PdsSystemItem" />
<table tableName="pds_system_level" domainObjectName="PdsSystemLevel" />
</context>
</generatorConfiguration>
将这个文件保存至你下载的mybatis-generator-core-1.3.1文件夹下 

第三步:用命令行运行(记得选择自己的文件地址)  
java -jar E:\soft\mybatis-generator-core-1.3.1\lib\mybatis-generator-core-1.3.1.jar -configfile E:\soft\mybatis-generator-core-1.3.1\genrator.xml -overwrite





我自己写的如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- classPathEntry:数据库的JDBC驱动,换成你自己的驱动位置 -->
<classPathEntry location="D:\abin\Java\MybatisTool\mybatis-generator-core-1.3.1\lib\mysql-connector-java-5.1.20-bin.jar" />
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 去除自动生成的注释 -->
<commentGenerator>
<property name="suppressAllComments" value="true" />
</commentGenerator>
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mycrm" userId="root" password="root">
</jdbcConnection>
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:自动生成代码的位置 -->
<javaModelGenerator targetPackage="com.test.model" targetProject="F:\nb\mp\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<sqlMapGenerator targetPackage="com.test.mapping"  targetProject="F:\nb\mp\src\main\java">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.test.dao"  targetProject="F:\nb\mp\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- tableName:用于自动生成代码的数据库表;domainObjectName:对应于数据库表的javaBean类名 -->
<table tableName="testcrm" domainObjectName="testcrmone" />
<table tableName="testmy" domainObjectName="testmyone" />
</context>
</generatorConfiguration>

posted @ 2012-06-21 18:10 abin 阅读(13814) | 评论 (1)编辑 收藏

     摘要: 1. 全局变量num=0   两个线程同时执行以下代码   {      for(int i=0;i<50;i++){         num+=1;      } ...  阅读全文
posted @ 2012-06-21 13:23 abin 阅读(511) | 评论 (0)编辑 收藏

多线程
线程:是指进程中的一个执行流程。
线程与进程的区别:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源。


如何创建一个线程?

创建线程有两种方式,如下:
1、 扩展java.lang.Thread类
2、 实现Runnable接口
Thread类代表线程类,它的两个最主要的方法是:
run()——包含线程运行时所执行的代码
Start()——用于启动线程

一个线程只能被启动一次。第二次启动时将会抛出java.lang.IllegalThreadExcetpion异常

线程间状态的转换(如图示)

新建状态:用new语句创建的线程对象处于新建状态,此时它和其它的java对象一样,仅仅在堆中被分配了内存
就绪状态:当一个线程创建了以后,其他的线程调用了它的start()方法,该线程就进入了就绪状态。处于这个状态的线程位于可运行池中,等待获得CPU的使用权
运行状态:处于这个状态的线程占用CPU,执行程序的代码
阻塞状态:当线程处于阻塞状态时,java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。
阻塞状态分为三种情况:
1、 位于对象等待池中的阻塞状态:当线程运行时,如果执行了某个对象的wait()方法,java虚拟机就回把线程放到这个对象的等待池中
2、 位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他的线程占用,JVM就会把这个线程放到这个对象的琐池中。
3、 其它的阻塞状态:当前线程执行了sleep()方法,或者调用了其它线程的join()方法,或者发出了I/O请求时,就会进入这个状态中。

死亡状态:当线程退出了run()方法,就进入了死亡状态,该线程结束了生命周期。
           或者正常退出
           或者遇到异常退出
           Thread类的isAlive()方法判断一个线程是否活着,当线程处于死亡状态或者新建状态时,该方法返回false,在其余的状态下,该方法返回true.

线程调度
线程调度模型:分时调度模型和抢占式调度模型
JVM采用抢占式调度模型。
所谓的多线程的并发运行,其实是指宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。
(线程的调度不是跨平台,它不仅取决于java虚拟机,它还依赖于操作系统)

如果希望明确地让一个线程给另外一个线程运行的机会,可以采取以下的办法之一
1、 调整各个线程的优先级
2、 让处于运行状态的线程调用Thread.sleep()方法
3、 让处于运行状态的线程调用Thread.yield()方法
4、 让处于运行状态的线程调用另一个线程的join()方法

调整各个线程的优先级
Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。
如果希望程序能够移值到各个操作系统中,应该确保在设置线程的优先级时,只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY这3个优先级。

线程睡眠:当线程在运行中执行了sleep()方法时,它就会放弃CPU,转到阻塞状态。
线程让步:当线程在运行中执行了Thread类的yield()静态方法时,如果此时具有相同优先级的其它线程处于就绪状态,那么yield()方法将把当前运行的线程放到运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。
Sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程,两者的区别在于:
         1、sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。
2、当线程执行了sleep(long millis)方法后,将转到阻塞状态,参数millis指定睡眠时间;当线程执行了yield()方法后,将转到就绪状态。
          3、sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常
          4、sleep()方法比yield()方法具有更好的移植性

等待其它线程的结束:join()
          当前运行的线程可以调用另一个线程的 join()方法,当前运行的线程将转到阻塞状态,直到另一个线程运行结束,它才恢复运行。

定时器Timer:在JDK的java.util包中提供了一个实用类Timer, 它能够定时执行特定的任务。

线程的同步
原子操作:根据Java规范,对于基本类型的赋值或者返回值操作,是原子操作。但这里的基本数据类型不包括long和double, 因为JVM看到的基本存储单位是32位,而long 和double都要用64位来表示。所以无法在一个时钟周期内完成。

自增操作(++)不是原子操作,因为它涉及到一次读和一次写。

原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。

同步代码块:为了保证每个线程能够正常执行原子操作,Java引入了同步机制,具体的做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。

同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。

当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。
1、 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。
2、 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。
(一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁
如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中)

线程同步的特征:
1、 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制)
2、 每个对象都有唯一的同步锁
3、 在静态方法前面可以使用synchronized修饰符。
4、 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。
5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。

线程安全的类:
1、 这个类的对象可以同时被多个线程安全的访问。
2、 每个线程都能正常的执行原子操作,得到正确的结果。
3、 在每个线程的原子操作都完成后,对象处于逻辑上合理的状态。

释放对象的锁:
1、 执行完同步代码块就会释放对象的锁
2、 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放
3、 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。

死锁
当一个线程等待由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时,就会发生死锁。JVM不监测也不试图避免这种情况,因此保证不发生死锁就成了程序员的责任。

如何避免死锁
一个通用的经验法则是:当几个线程都要访问共享资源A、B、C 时,保证每个线程都按照同样的顺序去访问他们。

线程通信
Java.lang.Object类中提供了两个用于线程通信的方法
1、 wait():执行了该方法的线程释放对象的锁,JVM会把该线程放到对象的等待池中。该线程等待其它线程唤醒
2、 notify():执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中。
posted @ 2012-06-17 01:42 abin 阅读(1570) | 评论 (0)编辑 收藏

来自:开发者在线 
Java多线程程序设计详细解析  
 
一、理解多线程

多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。

线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。

多个线程的执行是并发的,也就是在逻辑上“同时”,而不管是否是物理上的“同时”。如果系统只有一个CPU,那么真正的“同时”是不可能的,但是由于CPU的速度非常快,用户感觉不到其中的区别,因此我们也不用关心它,只需要设想各个线程是同时执行即可。

多线程和传统的单线程在程序设计上最大的区别在于,由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的,由此带来的线程调度,同步等问题,将在以后探讨。

二:在Java中实现多线程

我们不妨设想,为了创建一个新的线程,我们需要做些什么?很显然,我们必须指明这个线程所要执行的代码,而这就是在Java中实现多线程我们所需要做的一切!

真是神奇!Java是如何做到这一点的?通过类!作为一个完全面向对象的语言,Java提供了类java.lang.Thread来方便多线程编程,这个类提供了大量的方法来方便我们控制自己的各个线程,我们以后的讨论都将围绕这个类进行。

那么如何提供给 Java 我们要线程执行的代码呢?让我们来看一看 Thread 类。Thread 类最重要的方法是run(),它为Thread类的方法start()所调用,提供我们的线程所要执行的代码。为了指定我们自己的代码,只需要覆盖它!

方法一:继承 Thread 类,覆盖方法 run(),我们在创建的 Thread 类的子类中重写 run() ,加入线程所要执行的代码即可。下面是一个例子:



public class MyThread extends Thread 
{ 
 int count= 1, number; 
 public MyThread(int num)
{ 
  number = num; 
  System.out.println
("创建线程 " + number); 
 } 
 public void run() { 
  while(true) { 
   System.out.println
("线程 " + number + ":计数 " + count); 
   if(++count== 6) return; 
  } 
 } 
 public static void main(String args[])
{ 
  for(int i = 0; 
i 〈 5; i++) new MyThread(i+1).start(); 
 } 
}
 

这种方法简单明了,符合大家的习惯,但是,它也有一个很大的缺点,那就是如果我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承 Thread 类,这时如果我们又不想建立一个新的类,应该怎么办呢? 

我们不妨来探索一种新的方法:我们不创建Thread类的子类,而是直接使用它,那么我们只能将我们的方法作为参数传递给 Thread 类的实例,有点类似回调函数。但是 Java 没有指针,我们只能传递一个包含这个方法的类的实例。 

那么如何限制这个类必须包含这一方法呢?当然是使用接口!(虽然抽象类也可满足,但是需要继承,而我们之所以要采用这种新方法,不就是为了避免继承带来的限制吗?) 

Java 提供了接口 java.lang.Runnable 来支持这种方法。 

方法二:实现 Runnable 接口 

Runnable接口只有一个方法run(),我们声明自己的类实现Runnable接口并提供这一方法,将我们的线程代码写入其中,就完成了这一部分的任务。但是Runnable接口并没有任何对线程的支持,我们还必须创建Thread类的实例,这一点通过Thread类的构造函数public Thread(Runnable target);来实现。下面是一个例子: 


public class MyThread implements Runnable
{ 
 int count= 1, number; 
 public MyThread(int num)
{ 
  number = num; 
  System.out.println("创建线程 " + number); 
 } 
 public void run()
{ 
  while(true)
{ 
   System.out.println
("线程 " + number + ":计数 " + count); 
   if(++count== 6) return; 
  } 
 } 
 public static void main(String args[])
{ 
  for(int i = 0; i 〈 5;
i++) new Thread(new MyThread(i+1)).start(); 
 } 
}
 

严格地说,创建Thread子类的实例也是可行的,但是必须注意的是,该子类必须没有覆盖 Thread 类的 run 方法,否则该线程执行的将是子类的 run 方法,而不是我们用以实现Runnable 接口的类的 run 方法,对此大家不妨试验一下。 

使用 Runnable 接口来实现多线程使得我们能够在一个类中包容所有的代码,有利于封装,它的缺点在于,我们只能使用一套代码,若想创建多个线程并使各个线程执行不同的代码,则仍必须额外创建类,如果这样的话,在大多数情况下也许还不如直接用多个类分别继承 Thread 来得紧凑。 

综上所述,两种方法各有千秋,大家可以灵活运用。 

下面让我们一起来研究一下多线程使用中的一些问题。 

三、线程的四种状态 

1. 新状态:线程已被创建但尚未执行(start() 尚未被调用)。 

2. 可执行状态:线程可以执行,虽然不一定正在执行。CPU 时间随时可能被分配给该线程,从而使得它执行。 

3. 死亡状态:正常情况下 run() 返回使得线程死亡。调用 stop()或 destroy() 亦有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁。 

4. 阻塞状态:线程不会被分配 CPU 时间,无法执行。 

四、线程的优先级 

线程的优先级代表该线程的重要程度,当有多个线程同时处于可执行状态并等待获得 CPU 时间时,线程调度系统根据各个线程的优先级来决定给谁分配 CPU 时间,优先级高的线程有更大的机会获得 CPU 时间,优先级低的线程也不是没有机会,只是机会要小一些罢了。 

你可以调用 Thread 类的方法 getPriority() 和 setPriority()来存取线程的优先级,线程的优先级界于1(MIN_PRIORITY)和10(MAX_PRIORITY)之间,缺省是5(NORM_PRIORITY)。 

五、线程的同步 

由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。 

由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。 

1. synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如: 


public synchronized void accessVal(int newVal);
 

synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。 

这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。 

在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。 

synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。 

2. synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下: 


synchronized(syncObject)
{ 
//允许访问控制的代码 
}
 

synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。 

六、线程的阻塞 

为了解决对共享存储区的访问冲突,Java 引入了同步机制,现在让我们来考察多个线程对共享资源的访问,显然同步机制已经不够了,因为在任意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻准备好了的资源也可能不止一个。为了解决这种情况下的访问控制问题,Java 引入了对阻塞机制的支持。 

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),学过操作系统的同学对它一定已经很熟悉了。Java 提供了大量方法来支持阻塞,下面让我们逐一分析。 

1. sleep() 方法:sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。典型地,sleep() 被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。 

2. suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。 

3. yield() 方法:yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。 

4. wait() 和 notify() 方法:两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用。 

初看起来它们与 suspend() 和 resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了的话),而这一对方法则相反。 

上述的核心区别导致了一系列的细节上的区别。 

首先,前面叙述的所有方法都隶属于 Thread 类,但是这一对却直接隶属于 Object 类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。 

而调用 任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。 

其次,前面叙述的所有方法都可在任何位置调用,但是这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁可以释放。 

同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。 

wait() 和 notify() 方法的上述特性决定了它们经常和synchronized 方法或块一起使用,将它们和操作系统的进程间通信机制作一个比较就会发现它们的相似性:synchronized方法或块提供了类似于操作系统原语的功能,它们的执行不会受到多线程机制的干扰,而这一对方法则相当于 block 和wakeup 原语(这一对方法均声明为 synchronized)。 

它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法(如信号量算法),并用于解决各种复杂的线程间通信问题。关于 wait() 和 notify() 方法最后再说明两点: 

第一:调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。 

第二:除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。 

谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend() 方法和不指定超时期限的 wait() 方法的调用都可能产生死锁。遗憾的是,Java 并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。 

以上我们对 Java 中实现线程阻塞的各种方法作了一番分析,我们重点分析了 wait() 和 notify()方法,因为它们的功能最强大,使用也最灵活,但是这也导致了它们的效率较低,较容易出错。实际使用中我们应该灵活使用各种方法,以便更好地达到我们的目的。 

七、守护线程 

守护线程是一类特殊的线程,它和普通线程的区别在于它并不是应用程序的核心部分,当一个应用程序的所有非守护线程终止运行时,即使仍然有守护线程在运行,应用程序也将终止,反之,只要有一个非守护线程在运行,应用程序就不会终止。守护线程一般被用于在后台为其它线程提供服务。 

可以通过调用方法 isDaemon() 来判断一个线程是否是守护线程,也可以调用方法 setDaemon() 来将一个线程设为守护线程。 

八、线程组 

线程组是一个 Java 特有的概念,在 Java 中,线程组是类ThreadGroup 的对象,每个线程都隶属于唯一一个线程组,这个线程组在线程创建时指定并在线程的整个生命期内都不能更改。 

你可以通过调用包含 ThreadGroup 类型参数的 Thread 类构造函数来指定线程属的线程组,若没有指定,则线程缺省地隶属于名为 system 的系统线程组。 

在 Java 中,除了预建的系统线程组外,所有线程组都必须显式创建。在 Java 中,除系统线程组外的每个线程组又隶属于另一个线程组,你可以在创建线程组时指定其所隶属的线程组,若没有指定,则缺省地隶属于系统线程组。这样,所有线程组组成了一棵以系统线程组为根的树。 

Java 允许我们对一个线程组中的所有线程同时进行操作,比如我们可以通过调用线程组的相应方法来设置其中所有线程的优先级,也可以启动或阻塞其中的所有线程。 

Java 的线程组机制的另一个重要作用是线程安全。线程组机制允许我们通过分组来区分有不同安全特性的线程,对不同组的线程进行不同的处理,还可以通过线程组的分层结构来支持不对等安全措施的采用。 

Java 的 ThreadGroup 类提供了大量的方法来方便我们对线程组树中的每一个线程组以及线程组中的每一个线程进行操作。 

九、总结 

在本文中,我们讲述了 Java 多线程编程的方方面面,包括创建线程,以及对多个线程进行调度、管理。我们深刻认识到了多线程编程的复杂性,以及线程切换开销带来的多线程程序的低效性,这也促使我们认真地思考一个问题:我们是否需要多线程?何时需要多线程? 

多线程的核心在于多个代码块并发执行,本质特点在于各代码块之间的代码是乱序执行的。我们的程序是否需要多线程,就是要看这是否也是它的内在特点。 

假如我们的程序根本不要求多个代码块并发执行,那自然不需要使用多线程;假如我们的程序虽然要求多个代码块并发执行,但是却不要求乱序,则我们完全可以用一个循环来简单高效地实现,也不需要使用多线程;只有当它完全符合多线程的特点时,多线程机制对线程间通信和线程管理的强大支持才能有用武之地,这时使用多线程才是值得的。 

 来自:开发者在线
另外如果你喜欢电脑的话,我推荐你去初学者之路看看~
posted @ 2012-06-17 01:41 abin 阅读(482) | 评论 (0)编辑 收藏

多线程
线程:是指进程中的一个执行流程。
线程与进程的区别:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源。


如何创建一个线程?

创建线程有两种方式,如下:
1、 扩展java.lang.Thread类
2、 实现Runnable接口
Thread类代表线程类,它的两个最主要的方法是:
run()——包含线程运行时所执行的代码
Start()——用于启动线程

一个线程只能被启动一次。第二次启动时将会抛出java.lang.IllegalThreadExcetpion异常

线程间状态的转换(如图示)

新建状态:用new语句创建的线程对象处于新建状态,此时它和其它的java对象一样,仅仅在堆中被分配了内存
就绪状态:当一个线程创建了以后,其他的线程调用了它的start()方法,该线程就进入了就绪状态。处于这个状态的线程位于可运行池中,等待获得CPU的使用权
运行状态:处于这个状态的线程占用CPU,执行程序的代码
阻塞状态:当线程处于阻塞状态时,java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。
阻塞状态分为三种情况:
1、 位于对象等待池中的阻塞状态:当线程运行时,如果执行了某个对象的wait()方法,java虚拟机就回把线程放到这个对象的等待池中
2、 位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他的线程占用,JVM就会把这个线程放到这个对象的琐池中。
3、 其它的阻塞状态:当前线程执行了sleep()方法,或者调用了其它线程的join()方法,或者发出了I/O请求时,就会进入这个状态中。

死亡状态:当线程退出了run()方法,就进入了死亡状态,该线程结束了生命周期。
           或者正常退出
           或者遇到异常退出
           Thread类的isAlive()方法判断一个线程是否活着,当线程处于死亡状态或者新建状态时,该方法返回false,在其余的状态下,该方法返回true.

线程调度
线程调度模型:分时调度模型和抢占式调度模型
JVM采用抢占式调度模型。
所谓的多线程的并发运行,其实是指宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。
(线程的调度不是跨平台,它不仅取决于java虚拟机,它还依赖于操作系统)

如果希望明确地让一个线程给另外一个线程运行的机会,可以采取以下的办法之一
1、 调整各个线程的优先级
2、 让处于运行状态的线程调用Thread.sleep()方法
3、 让处于运行状态的线程调用Thread.yield()方法
4、 让处于运行状态的线程调用另一个线程的join()方法

调整各个线程的优先级
Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。
如果希望程序能够移值到各个操作系统中,应该确保在设置线程的优先级时,只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY这3个优先级。

线程睡眠:当线程在运行中执行了sleep()方法时,它就会放弃CPU,转到阻塞状态。
线程让步:当线程在运行中执行了Thread类的yield()静态方法时,如果此时具有相同优先级的其它线程处于就绪状态,那么yield()方法将把当前运行的线程放到运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。
Sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程,两者的区别在于:
         1、sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。
2、当线程执行了sleep(long millis)方法后,将转到阻塞状态,参数millis指定睡眠时间;当线程执行了yield()方法后,将转到就绪状态。
          3、sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常
          4、sleep()方法比yield()方法具有更好的移植性

等待其它线程的结束:join()
          当前运行的线程可以调用另一个线程的 join()方法,当前运行的线程将转到阻塞状态,直到另一个线程运行结束,它才恢复运行。

定时器Timer:在JDK的java.util包中提供了一个实用类Timer, 它能够定时执行特定的任务。

线程的同步
原子操作:根据Java规范,对于基本类型的赋值或者返回值操作,是原子操作。但这里的基本数据类型不包括long和double, 因为JVM看到的基本存储单位是32位,而long 和double都要用64位来表示。所以无法在一个时钟周期内完成。

自增操作(++)不是原子操作,因为它涉及到一次读和一次写。

原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。

同步代码块:为了保证每个线程能够正常执行原子操作,Java引入了同步机制,具体的做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。

同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。

当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。
1、 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。
2、 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。
(一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁
如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中)

线程同步的特征:
1、 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制)
2、 每个对象都有唯一的同步锁
3、 在静态方法前面可以使用synchronized修饰符。
4、 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。
5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。

线程安全的类:
1、 这个类的对象可以同时被多个线程安全的访问。
2、 每个线程都能正常的执行原子操作,得到正确的结果。
3、 在每个线程的原子操作都完成后,对象处于逻辑上合理的状态。

释放对象的锁:
1、 执行完同步代码块就会释放对象的锁
2、 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放
3、 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。

死锁
当一个线程等待由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时,就会发生死锁。JVM不监测也不试图避免这种情况,因此保证不发生死锁就成了程序员的责任。

如何避免死锁
一个通用的经验法则是:当几个线程都要访问共享资源A、B、C 时,保证每个线程都按照同样的顺序去访问他们。

线程通信
Java.lang.Object类中提供了两个用于线程通信的方法
1、 wait():执行了该方法的线程释放对象的锁,JVM会把该线程放到对象的等待池中。该线程等待其它线程唤醒
2、 notify():执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中。
posted @ 2012-06-17 01:24 abin 阅读(317) | 评论 (0)编辑 收藏

这两个方法主要来源是,sleep用于线程控制,而wait用于线程间的通信,与wait配套的方法还有notify和notifyAll.

区别一:

sleep是Thread类的方法,是线程用来 控制自身流程的,比如有一个要报时的线程,每一秒中打印出一个时间,那么我就需要在print方法前面加上一个sleep让自己每隔一秒执行一次。就像个闹钟一样。

wait是Object类的方法,用来线程间的通信,这个方法会使当前拥有该对象锁的进程等待知道其他线程调用notify方法时再醒来,不过你也可以给他指定一个时间,自动醒来。这个方法主要是用走不同线程之间的调度的。

区别二 :

关于锁的释放 ,在这里假设大家已经知道了锁的概念及其意义。调用sleep方法不会释放锁(自己的感觉是sleep方法本来就是和锁没有关系的,因为他是一个线程用于管理自己的方法,不涉及线程通信)

JDK 7 中的解释:

“public static void sleep(long millis)

throws InterruptedException
Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.The thread does not lose ownership of any monitors.

public final void wait() throws InterruptedException
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.“
调用wait方法会释放当前线程的锁(其实线程间的通信是靠对象来管理的,所有操作一个对象的线程是这个对象通过自己的wait方法来管理的,就好像这个对象是电视机,三个人是三个线程,那么电视机的遥控器就是这个锁,假如现在A拿着遥控器,电视机调用wait方法,那么A就交出自己的遥控器,由jVM虚拟机调度,遥控器该交给谁。)【我想到一个好玩的例子:如果A拿遥控器的期间,他可以用自己的sleep每隔十分钟调一次电视台,而在他调台休息的十分钟期间,遥控器还在他的手上~】

区别三:

使用区域

由于wait函数的特殊意义,所以他是应该放在同步语句块中的,这样才有意义 。

注意:两个方法都需要抛出异常

个人见解:有sleep和wait的第二个区别,引起了我对Java线程机制的一个疑问,目前还没有看过JDk这方面的源码(其实看了,是木有看懂),线程的同步管理,是不是由对象在调度,如果是对象在调度,那么JDK 1.5新引入的ReentrantLock机制就比synchronized关键字更值得提倡。因为他更能反映出这么一个机制来。好多人不能理解wait和sleep的区别,我认为就是因为synchronized关键字的影响。当然自己还不懂JAVA的线程具体实现,留作疑问以后有时间继续研究吧





Java中的多线程是一种抢占式的机制 而不是分时机制。抢占式机制指的是有多个线程处于可运行状态,但是只有一个线程在运行。

共同点:
1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。

2. wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。
   如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
   需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException。

不同点:
1. Thread类的方法:sleep(),yield()等
   Object的方法:wait()和notify()等
  
2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
   sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常   

 

 

 线程的调度
        线程调度器按线程的优先级高低选择高优先级线程(进入运行中状态)执行,同时线程调度是抢先式调度,即如果在当前线程执行过程中,一个更高优先级的线程进入可运行状态,则这个线程立即被调度执行。


抢先式调度又分为:时间片方式和独占方式。在时间片方式下,当前活动线程执行完当前时间片后,如果有其他处于就绪状态的相同优先级的线程,系统会将执行权交给其他就绪态的同优先级线程;当前活动线程转入等待执行队列,等待下一个时间片的调度。
在独占方式下,当前活动线程一旦获得执行权,将一直执行下去,直到执行完毕或由于某种原因主动放弃CPU,或者是有一高优先级的线程处于就绪状态。

posted @ 2012-06-17 01:15 abin 阅读(361) | 评论 (0)编辑 收藏

摘 要:介绍了Servlet多线程机制,通过一个实例并结合Java 的内存模型说明引起Servlet线程不安全的原因,给出了保证Servlet线程安全的三种解决方案,并说明三种方案在实际开发中的取舍。

  关键字:Servlet 线程安全 同步 Java内存模型 实例变量 

  Servlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的安全性问题。然而,很多人编写Servlet/JSP程序时并没有注意到多线程安全性的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题。

  Servlet的多线程机制

  
  Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行,如图1所示。


图1 Servlet线程池

  这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。

  Servlet的线程安全问题

  Servlet的线程安全问题主要是由于实例变量使用不当而引起的,这里以一个现实的例子来说明。

Import javax.servlet. *; 
Import javax.servlet.http. *; 
Import java.io. *; 
Public class Concurrent Test extends HttpServlet {PrintWriter output; 
Public void service (HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {String username;
Response.setContentType ("text/html; charset=gb2312");
Username = request.getParameter ("username"); 
Output = response.getWriter (); 
Try {Thread. sleep (5000); //为了突出并发问题,在这设置一个延时
} Catch (Interrupted Exception e){}
output.println("用户名:"+Username+"<BR>"); 
}
}

  该Servlet中定义了一个实例变量output,在service方法将其赋值为用户的输出。当一个用户访问该Servlet时,程序会正常的运行,但当多个用户并发访问时,就可能会出现其它用户的信息显示在另外一些用户的浏览器上的问题。这是一个严重的问题。为了突出并发问题,便于测试、观察,我们在回显用户信息时执行了一个延时的操作。假设已在web.xml配置文件中注册了该Servlet,现有两个用户a和b同时访问该Servlet(可以启动两个IE浏览器,或者在两台机器上同时访问),即同时在浏览器中输入:

  a: http://localhost: 8080/servlet/ConcurrentTest? Username=a

  b: http://localhost: 8080/servlet/ConcurrentTest? Username=b

  如果用户b比用户a回车的时间稍慢一点,将得到如图2所示的输出:


图2 a用户和b用户的浏览器输出

  从图2中可以看到,Web服务器启动了两个线程分别处理来自用户a和用户b的请求,但是在用户a的浏览器上却得到一个空白的屏幕,用户a的信息显示在用户b的浏览器上。该Servlet存在线程不安全问题。下面我们就从分析该实例的内存模型入手,观察不同时刻实例变量output的值来分析使该Servlet线程不安全的原因。

  Java的内存模型JMM(Java Memory Model)JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。根据JMM,我们可以将论文中所讨论的Servlet实例的内存模型抽象为图3所示的模型。


图3 Servlet实例的JMM模型

  下面根据图3所示的内存模型,来分析当用户a和b的线程(简称为a线程、b线程)并发执行时,Servlet实例中所涉及变量的变化情况及线程的执行情况,如图4所示。

调度时刻 a线程 b线程
T1 访问Servlet页面  
T2   访问Servlet页面
T3 output=a的输出username=a休眠5000毫秒,让出CPU  
T4   output=b的输出(写回主存)username=b休眠5000毫秒,让出CPU
T5 在用户b的浏览器上输出a线程的username的值,a线程终止。  
T6   在用户b的浏览器上输出b线程的username的值,b线程终止。
                  图4 Servlet实例的线程调度情况

  从图4中可以清楚的看到,由于b线程对实例变量output的修改覆盖了a线程对实例变量output的修改,从而导致了用户a的信息显示在了用户b的浏览器上。如果在a线程执行输出语句时,b线程对output的修改还没有刷新到主存,那么将不会出现图2所示的输出结果,因此这只是一种偶然现象,但这更增加了程序潜在的危险性。 

 

 

设计线程安全的Servlet

  通过上面的分析,我们知道了实例变量不正确的使用是造成Servlet线程不安全的主要原因。下面针对该问题给出了三种解决方案并对方案的选取给出了一些参考性的建议。

  1、实现 SingleThreadModel 接口

  该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。这种方法只要将前面的Concurrent Test类的类头定义更改为:

Public class Concurrent Test extends HttpServlet implements SingleThreadModel {
…………
}


  2、同步对共享数据的操作

  使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,在本论文中的Servlet可以通过同步块操作来保证线程的安全。同步后的代码如下: 

…………
Public class Concurrent Test extends HttpServlet { …………
Username = request.getParameter ("username"); 
Synchronized (this){
Output = response.getWriter (); 
Try {
Thread. Sleep (5000);
} Catch (Interrupted Exception e){}
output.println("用户名:"+Username+"<BR>"); 
} 
}
}


  3、避免使用实例变量

  本实例中的线程安全问题是由实例变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量,那么该Servlet就是线程安全的。

  修正上面的Servlet代码,将实例变量改为局部变量实现同样的功能,代码如下:

…… 
Public class Concurrent Test extends HttpServlet {public void service (HttpServletRequest request, HttpServletResponse 
Response) throws ServletException, IOException {
Print Writer output; 
String username;
Response.setContentType ("text/html; charset=gb2312");
…… 
} 
}


  对上面的三种方法进行测试,可以表明用它们都能设计出线程安全的Servlet程序。但是,如果一个Servlet实现了SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销。SingleThreadModel在Servlet2.4中已不再提倡使用;同样如果在程序中使用同步来保护要使用的共享的数据,也会使系统的性能大大下降。这是因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。另外为保证主存内容和线程的工作内存中的数据的一致性,要频繁地刷新缓存,这也会大大地影响系统的性能。所以在实际的开发中也应避免或最小化 Servlet 中的同步代码;在Serlet中避免使用实例变量是保证Servlet线程安全的最佳选择。从Java 内存模型也可以知道,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。

  小结

  Servlet的线程安全问题只有在大量的并发访问时才会显现出来,并且很难发现,因此在编写Servlet程序时要特别注意。线程安全问题主要是由实例变量造成的,因此在Servlet中应避免使用实例变量。如果应用程序设计无法避免使用实例变量,那么使用同步来保护要使用的实例变量,但为保证系统的最佳性能,应该同步可用性最小的代码路径。

 

posted @ 2012-06-14 09:12 abin 阅读(534) | 评论 (0)编辑 收藏

java动态代理和cglib动态代理在工作中用代理的地方非常多,但一直还没仔细来看代理的原理,今天被同事提到,所以自己开始仔细研究了一下这两者代理都做了些什么工作,并通过编写测试用例的方式来对两种代理原理作理解。
在自行看代码之前,初步问了一下朋友,大概解释这两者区别是,java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。这是朋友说的,我并没自己实验过,所以也没映象,所以开始自己动手实践之:
java动态代理

使用方法:

接口:

public interface Call {
void doCall(String doCall);
}

public interface Processor {
void doProcess(String doProcess);
}

实现类:

public class ServiceImpl implements Call, Processor {

public void doCall(String doCall) {
System.out.println("doCall");
}

public void doProcess(String doProcess) {
System.out.println("doProcess");
}
}

具体代理Handler:

public class ServiceHandler implements InvocationHandler {

private Call callService;

public ServiceHandler(Call callService) {
this.callService = callService;
}

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("proxyMethod=" + method.getName());
Object obj = method.invoke(this.callService, args);
System.out.println("after invoke!");
return obj;
}

}

使用java动态代理:

public class JdkProxyTest {   
@Test
public void testJdkProxy() {
Call call = new ServiceImpl();
ServiceHandler handler = new ServiceHandler(call);
Call callProxy = (Call) Proxy.newProxyInstance(call.getClass().getClassLoader(),
new Class[]{Call.class}, handler);
callProxy.doCall("test");
}
}

最终效果就是执行代理接口的doCall方法之前,该方法被ServiceHandler给处理了。

通过查看java.lang.reflect.Proxy代码,大致拟了一下它的实现原理:
1. 取到new Class[]{Call.class}这里所有接口,通过Class.forName把接口类加载到JVM,放到内部Set里保存,把接口的完善名字保存,带包名的接口名字,并以把这组接口名称数组转换成List作为key,用于下面生成代理类后保存到内部Map的key.也就是相当于这一组的接口名称对应的一个生成的代理类
2. 主要是从内存里找是否之前已经生成好了这同一组接口的代理类,如果有就直接拿出。这里第一次是需要新建立的,所以开始创建代理,首先检查代理目标接口的访问控制符是否是默认包级别的,如果是就需要给生成的代理类设置目标接口同样的包名,才能默认访问这种级别下的接口。如果这种有默认访问控制标识符的目标接口,又有不同包名的目标接口,则会报出错误。否则其它情况,是给的无包名的代理类,生成的代理类的默认名称是$Proxy开头加Proxy里标识唯一类名的数字,是静态long型变量,每次生成一次代理类会累加
3. 调用ProxyGenerator.generateProxyClass(proxyName, interfaces)动态生成class字节码类,该类相当于是Proxy的子类,实现了需要代理的接口方法,并在每个方法里调用了InvocationHandler的invoke方法,而我们自己实现的InvocationHandler接口类里完成了以反射方式最终对目标业务类的接口方法进行调用。所以此种方式实现的动态代理只能代理接口方法,对具体类的代理不能实现。

 

http://hi.baidu.com/dobug/blog/item/493f817e802479340cd7dab9.html

posted @ 2012-06-07 21:51 abin 阅读(699) | 评论 (0)编辑 收藏

sleep,wait,suspend,resume,join,interrupt,stop,destory
posted @ 2012-06-06 14:19 abin 阅读(338) | 评论 (0)编辑 收藏

仅列出标题
共50页: First 上一页 37 38 39 40 41 42 43 44 45 下一页 Last