January 9, 2012
| 作者:白菜
|
分类:JAVA
简介: Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。在这期的Java 理论与实践中,Brian Goetz 将介绍几种正确使用 volatile 变量的模式,并针对其适用性限制提出一些建议。
Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。本文介绍了几种有效使用 volatile 变量的模式,并强调了几种不适合使用 volatile 变量的情形。
锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。
Volatile 变量
Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。因此,单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类(例如 “start <=end”)。
出于简易性或可伸缩性的考虑,您可能倾向于使用 volatile 变量而不是锁。当使用 volatile 变量而非锁时,某些习惯用法(idiom)更加易于编码和阅读。此外,volatile 变量不会像锁那样造成线程阻塞,因此也很少造成可伸缩性问题。在某些情况下,如果读操作远远大于写操作,volatile 变量还可以提供优于锁的性能优势。
正确使用 volatile 变量的条件
您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
- 对变量的写操作不依赖于当前值。
- 该变量没有包含在具有其他变量的不变式中。
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。实现正确的操作需要使 x 的值在操作期间保持不变,而 volatile 变量无法实现这点。(然而,如果将值调整为只从单个线程写入,那么可以忽略第一个条件。)
大多数编程情形都会与这两个条件的其中之一冲突,使得 volatile 变量不能像 synchronized 那样普遍适用于实现线程安全。清单 1 显示了一个非线程安全的数值范围类。它包含了一个不变式 —— 下界总是小于或等于上界。
清单 1. 非线程安全的数值范围类@NotThreadSafe
public class NumberRange {
private int lower, upper;
public int getLower() { return lower; }
public int getUpper() { return upper; }
public void setLower(int value) {
if (value > upper)
throw new IllegalArgumentException(...);
lower = value;
}
public void setUpper(int value) {
if (value < lower)
throw new IllegalArgumentException(...);
upper = value;
}
}
阅读剩余部分...
January 9, 2012
| 作者:白菜
|
分类:JAVA
在Java Concurrency in Practice中是这样定义线程安全的:
当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的。
显然只有资源竞争时才会导致线程不安全,因此。
原子操作的描述是: 多个线程执行一个操作时,其中,那么这个操作就是原子的。
枯燥的定义介绍完了,下面说更枯燥的理论知识。
Java语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。这个过程通过叫做指令的重排序。指令重排序存在的意义在于:JVM能够根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器的性能。
程序执行最简单的模型是按照指令出现的顺序执行,这样就与执行指令的CPU无关,最大限度的保证了指令的可移植性。这个模型的专业术语叫做顺序化一致性模型。但是现代计算机体系和处理器架构都不保证这一点(因为人为的指定并不能总是保证符合CPU处理的特性)。
我们来看最经典的一个案例。
阅读剩余部分...
January 9, 2012
| 作者:白菜
|
分类:JAVA
刚用了一个数据库连接池,在公司电脑上测试时报错
无法解析类型 java.sql.Wrapper。从必需的 .class 文件间接引用了它
找了下原因,原来这个Wrapper类是JDK1.6 版本才提供的,公司的环境是JDK 1.5。。。。
百度下,发现类似的也很多,大多是JDK版本过低的问题,也有包引用错了导致的。
好吧,附上我的任意切换JDK版本的方法。
假设我现在安装的是旧版本的JDK1.5,那么在系统环境变量里我们设置了java_home和classpath,对应存放的注册表项是(HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session Manager/Environment),JDK在安装的过程当中将在注册表会生成如下3个项目:
HKEY_LOCAL_MACHINE/SOFTWARE/JavaSoft/Java Development Kit
HKEY_LOCAL_MACHINE/SOFTWARE/JavaSoft/Java Plug-in
HKEY_LOCAL_MACHINE/SOFTWARE/JavaSoft/Java Runtime Environment
同时,JDK安装程序将会把java.exe,javaw.exe,javaws.exe这3个可执行文件拷贝到%SystemRoot%\system32目录下,由于%SystemRoot%\system32被操作系统缺省的设置为最高优先权的PATH搜索路径,因此可保证用户在命令行任何目录下可运行java.exe来启动JVM。
所以,
第一步是备份上面提到的四个注册表项和那三个可执行文件;
第二步,安装高版本的JDK 1.7,修改环境变量,然后备份上面提到的四个注册表项和那三个可执行文件;
第三步:把第一步得到的所有文件放到一个命名“1.5”文件夹中,把第二步得到的所有文件放到一个命名“1.7”文件夹中;
第四步:在每个文件夹下写如下的脚本:
@echo off
echo 设置JDK 1.5……
dir
copy /y java.exe %SystemRoot%\system32
copy /y javaw.exe %SystemRoot%\system32
copy /y javaws.exe %SystemRoot%\system32
pause
目录结构如下
├─1.5
│ 1.5.bat
│ java.exe
│ javaw.exe
│ javaws.exe
│ jdk1.5.reg
│ jdk1.5_.reg
│
├─1.7
│ 1.7.bat
│ java.exe
│ javaw.exe
│ javaws.exe
│ jdk 1.7_.reg
│ jdk1.7.reg
1.7文件夹下的只需要改下提示就可以了。现在的JAVA环境已经是JDK1.7了,我们到1.5下执行“1.5.bat”,导入注册表,JAVA环境就降成1.5了,反之,到1.7文件夹下执行“1.7..bat”,导入注册表,环境就变成1.7了,可以随意切换。
如果用eclipse的话,只要装一个高版本的JDK即可,在项目属性上设置JRE兼容性,不用这么麻烦。但对于netbeans则比较麻烦,可以借鉴此文。
January 5, 2012
| 作者:白菜
|
分类:JAVA
注:转自http://www.unclejoey.com/?p=554,有部分改动
摘要
常见的服务器会将用户post的数据保存在hashmap中. 而向hashmap中插入n对元素的时间复杂度大约是O(n), 但如果精心构造key使得每个key的hash值相同(也就是产生了碰撞),则时间复杂度会恶化到O(n^2),导致消耗大量的CPU时间.在java中,字符串的hash函数采用DJBX33A,只不过常数因子改为了31. 这样的函数有个特点,即如果字符串X, Y的hash值相同,那么X,Y都添加任意相同的前缀或后缀的以后的hash值也都相同.比如: "rQ"与 "qp"的hash值相同, 则"rQrQ", "rQqp", "qprQ", "qpqp" 这四个也相同,继续这个模式就可以很容易构造出 2^n 个 2*n长度的不同字符串
PS: JDK中关于hashCode()方法的实现,可用以下公式表达:

代码
package com.unclejoey.just4fun;
import java.math.BigDecimal;
public class HashCollision {
private static final int i1 = 48;
private static final int i2 = 8;
private static final int i3 = 31;
private static final int i4 = 60000;
private static final long l1 = i3 -1;
private static final long l2 = 2l << 32;
private static final BigDecimal d1 = new BigDecimal(31);
private static final BigDecimal d2 = d1.pow(i2);
private static final BigDecimal d3 = new BigDecimal(l2);
public static void main(String[] args) {
String t = "test_string";
for(int i=0; i<=i4; i++) {
String s = String.valueOf(i);
while(s.length() < 5){
s = "0" + s;
}
int hs = s.hashCode();
char[] r = g(hs, t.hashCode());
s = s.concat(new String(r));
if (s.hashCode() != t.hashCode()) {
System.err.println("NO WAY, I Couldn't be wrong...");
System.exit(1);
}
System.out.println(s);
}
}
private static char[] g(int s, int t) {
long hx1 = l1 * s + i1;
BigDecimal hx2 = d2.multiply(new BigDecimal(hx1)).subtract(new BigDecimal(i1));
BigDecimal hx3 = hx2.divide(new BigDecimal(l1));
BigDecimal hx4 = new BigDecimal(t).subtract(hx3);
BigDecimal b = hx4.divideToIntegralValue(d3.multiply(d3));
long l = hx4.subtract(b).longValue();
l = (l+l2) % l2;
if (l < 0) l += l2;
char[] c = new char[i2];
int p = 0;
while (l != 0) {
c[p++] = (char) (l % (i3) + i1);
l = l / i3;
}
int f = i2 - p;
char[] cs = new char[i2];
int i = 0;
while (i < f) {
cs[i++] = (char) i1;
}
while (i < i2) {
cs[i] = c[p - i + f - 1];
++i;
}
return cs;
}
}
注:以上代码为原博文作者的代码,不保证运行效果。详细的文档看这里,
http://www.nruns.com/_downloads/advisory28122011.pdf 。如果是phper,看这里:
http://www.laruence.com/2011/12/29/2412.html