转:JVM伪共享

   伪共享False sharing说明JVM底层技术也不让人那么放心。

内存缓存系统中基本单元是高速缓存行(Cache lines). cpu会把数据从内存加载到高速缓存中 ,这样可以获得更好的性能,高速缓存默认大小是64 Byte为一个区域,一个区域在一个时间点只允许一个核心操作,也就是说不能有多个核心同时操作一个缓存区域。

因为高速缓存是64字节,而Hotspot JVM的对象头是两个部分组成,第一部分是由24字节的hash code和8字节的锁等状态标识组成,第二部分是指向该对象类的引用。基本类型字节如下:
doubles (8) and longs (8)
ints (4) and floats (4)
shorts (2) and chars (2)
booleans (1) and bytes (1)
references (4/8)

因此,一个高速缓存64字节可以放下多个字段,如果这多个字段位于同一个高速缓存区,虽然它们是类的不同字段,如下代码:
Class A{
   int x;
   int y;
}
x和y被放在同一个高速缓存区,如果一个线程修改x;那么另外一个线程修改y,必须等待x修改完成后才能实施。
虽然两个线程修改各种独立变量,但是因为这些独立变量被放在同一个高速缓存区,性能就影响了。测试结果如后面。
当多核CPU线程同时修改在同一个高速缓存行各自独立的变量时,会不自不觉地影响性能,这就发生了伪共享False sharing,伪共享是性能的无声杀手。

解决方便是将高速缓存剩余的字节填充填满(pad),确保不发生多个字段被挤入一个高速缓存区,下面测试结果图就是和填充后性能比较。

实现字节填充的框架有 Disruptor,在RingBuffer中实现填充。关于Disruptor可见infoQ这个视频,用1毫秒的延时得到100K+ TPS吞吐量,JDK的ArrayQueue并行环境不见得是最快的,该视频后面讨论很多,让人大跌眼镜啊,开放源码多有好处啊,别人能发现你不能发现的漏洞。另外一篇解剖Disruptor

C#也有类似伪共享发生,见这里
jvm_False_sharing.png
参考:http://www.jdon.com/jivejdon/thread/42451
LMAX架构
disruptor - 并发编程框架
http://www.infoq.com/presentations/LMAX

转:正确使用 Volatile 变量

简介: 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;
}
}

    

阅读剩余部分...

转:指令重排序与happens-before法则

Java Concurrency in Practice中是这样定义线程安全的:


当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的。


显然只有资源竞争时才会导致线程不安全,因此

原子操作的描述是: 多个线程执行一个操作时,其中,那么这个操作就是原子的。

枯燥的定义介绍完了,下面说更枯燥的理论知识。

Java语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。这个过程通过叫做指令的重排序。指令重排序存在的意义在于:JVM能够根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器的性能。

程序执行最简单的模型是按照指令出现的顺序执行,这样就与执行指令的CPU无关,最大限度的保证了指令的可移植性。这个模型的专业术语叫做顺序化一致性模型。但是现代计算机体系和处理器架构都不保证这一点(因为人为的指定并不能总是保证符合CPU处理的特性)。

我们来看最经典的一个案例。

阅读剩余部分...

无法解析类型 java.sql.Wrapper。从必需的 .class 文件间接引用了它

刚用了一个数据库连接池,在公司电脑上测试时报错

无法解析类型 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了,可以随意切换。
turn_jdk_Environment.png

如果用eclipse的话,只要装一个高版本的JDK即可,在项目属性上设置JRE兼容性,不用这么麻烦。但对于netbeans则比较麻烦,可以借鉴此文。

转:Java Hash Algorithm Collision Practice (构造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

Android SDK 2.2开发环境安装

    昨晚安装了下安卓,鉴于安卓版本比较混乱,而且目前的主流版本是2.2,2.3,而且我手上的环境是2.2的,我选择的是比较保守的2.2的安装。在安装时遇到不少困惑,和书上以及视频里讲解的一些地方不太相同,大概是google频繁更新的问题吧,而且网上的一些教程有点老,会让新手困惑。这里记一下,供需要的参考。

1.第一步仍然是下载SDK的安装包,网址http://developer.android.com/index.html ,在down里可以看到最新的适合windows的版本是http://dl.google.com/android/android-sdk_r15-windows.zip,直接下载最新的,然后解压到e:\dev这样的目录中。

2.然后新建以下 几个文件夹
platforms,docs,samples,usb_driver,market_licensing,删除tools全部内容。

3.用迅雷等下载工具下载以下安装包,我们只下载2.0以上的SDK。8对应的是android 2.2,7对应的是android 2.1,往前类推。
谷歌api的安装包:
http://dl-ssl.google.com/android/repository/google_apis-5_r01.zip
http://dl-ssl.google.com/android/repository/google_apis-6_r01.zip
http://dl-ssl.google.com/android/repository/google_apis-7_r01.zip
http://dl-ssl.google.com/android/repository/google_apis-8_r02.zip

阅读剩余部分...

java nio 之MappedByteBuffer,高效文件/内存映射

MappedByteBuffer是java nio引入的文件内存映射方案,读写性能极高。NIO最主要的就是实现了对异步操作的支持。其中一种通过把一个套接字通道(SocketChannel)注册到一个选择器(Selector)中,不时调用后者的选择(select)方法就能返回满足的选择键(SelectionKey),键中包含了SOCKET事件信息。这就是select模型。
    SocketChannel的读写是通过一个类叫ByteBuffer(java.nio.ByteBuffer)来操作的.这个类本身的设计是不错的,比直接操作byte[]方便多了. ByteBuffer有两种模式:直接/间接.间接模式最典型(也只有这么一种)的就是HeapByteBuffer,即操作堆内存 (byte[]).但是内存毕竟有限,如果我要发送一个1G的文件怎么办?不可能真的去分配1G的内存.这时就必须使用"直接"模式,即 MappedByteBuffer,文件映射.
     先中断一下,谈谈操作系统的内存管理.一般操作系统的内存分两部分:物理内存;虚拟内存.虚拟内存一般使用的是页面映像文件,即硬盘中的某个(某些)特殊的文件.操作系统负责页面文件内容的读写,这个过程叫"页面中断/切换". MappedByteBuffer也是类似的,你可以把整个文件(不管文件有多大)看成是一个ByteBuffer.MappedByteBuffer 只是一种特殊的 ByteBuffer ,即是ByteBuffer的子类。 MappedByteBuffer 将文件直接映射到内存(这里的内存指的是虚拟内存,并不是物理内存)。通常,可以映射整个文件,如果文件比较大的话可以分段进行映射,只要指定文件的那个部分就可以。

三种方式:
              FileChannel提供了map方法来把文件影射为内存映像文件: MappedByteBuffer map(int mode,long position,long size); 可以把文件的从position开始的size大小的区域映射为内存映像文件,mode指出了 可访问该内存映像文件的方式:READ_ONLY,READ_WRITE,PRIVATE.                    
a. READ_ONLY,(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException.(MapMode.READ_ONLY)
 b. READ_WRITE(读/写): 对得到的缓冲区的更改最终将传播到文件;该更改对映射到同一文件的其他程序不一定是可见的。 (MapMode.READ_WRITE)
c. PRIVATE(专用): 对得到的缓冲区的更改不会传播到文件,并且该更改对映射到同一文件的其他程序也不是可见的;相反,会创建缓冲区已修改部分的专用副本。 (MapMode.PRIVATE)

三个方法:

a. fore();缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件
b. load()将缓冲区的内容载入内存,并返回该缓冲区的引用
c. isLoaded()如果缓冲区的内容在物理内存中,则返回真,否则返回假

三个特性:

    调用信道的map()方法后,即可将文件的某一部分或全部映射到内存中,映射内存缓冲区是个直接缓冲区,继承自ByteBuffer,但相对于ByteBuffer,它有更多的优点:

a. 读取快
b. 写入快
c. 随时随地写入

下面来看代码:

阅读剩余部分...

转:Hadoop部署

最近在自己的机器上部署了Hadoop0.18,在这里记录一下过程:

操作系统使用CentOS 5

每一步后面括号中标明操作的对象,“对所有节点”是在所有节点上都执行一次。“对主节点”只需按要求在主节点执行即可。


1.安装JDK(对所有节点)


在网上下载JDK程序包。
这里以jdk1.5.0_14为例,并安装在/home目录下。


2 安装Hadoop(对主节点)


在http://www.apache.org/dist/hadoop/core下载Hadoop,选择一个版本,例如:hadoop-0.18.3.tar.gz,安装至/home目录下。


3配置Hadoop配置文件(对主节点)


这里假设有三台机器,主节点10.63.0.51,从节点10.63.0.52和10.63.0.53。多台机器的配置可依此类推。
在/home/hadoop-0.18.3/conf下,配置hadoop-site.xml文件如下:

阅读剩余部分...

    Page :
  1. 1
  2. 2
  3. 3
  4. 4
  5. ...
  6. 6