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
December 30, 2011
| 作者:白菜
|
分类:默认分类
近期网络流传的纷纷扬扬的用户密码泄露事件大部分来自于这个所谓的安全平台-乌云系统
http://www.wooyun.org/,一瞬间,此网站可谓是挣足了眼球和面子,赢得了不少流量和关注,可是细细一想,总觉得有点猫腻。
(1)此网站实为一个松散的虚拟组织,任何人都可以在上面发布漏洞,我也看到过认识的人的影子。上面还有PPC的会员的影子。试问,这么混乱,质量能得到保证不?只要提交漏洞就能得到邀请码,成为其会员,也就跻身于安全研究人员的行业。
(2)就从其近日发布的密码泄露来看,一次比一次不靠谱,什么支付宝密码泄露,银行密码泄露,一次比一次夸张,一次比一次规模庞大,动辄几千万,上亿,但是就从其给出的数据看,却经不起推敲。首先这些泄漏事件越来越让人不相信,其次已有相关企业出来辟谣,指出其泄露的银行卡号都是废卡。另外,从其泄露的数据来看,字段项也显得不够专业,更像是凭空臆想出来的。再者从我所了解到的大企业数据中心的情况来看,也不可能拿到这么多数据。很多企业的安全系统可谓是固若金汤,都是硬件级别的安全,经得起推敲的,是国际上引进的军方级别的安全系统,有这么容易破解?很多大公司的办公电脑都是硬件加密,没有U盘无法启动,系统一旦有被入侵和破坏倾向,立刻自动销毁数据。
(3)此网站惯用虚虚实实的招数,先弄一些比较小的不起眼的,容易得到的漏洞来垫底,然后就是五花八门的泄露,其泄露的数据也时虚虚实实,很多是比较老旧的数据再加上一堆捏造的数据。其所展示的数据和技术也不够专业。
最后,虽然不太清楚这个所谓的乌云系统的数据来自哪里(此网站官方已宣布关闭),是否是第一数据源,但是就其近期所发布的事件而言,是越来越让人不信任。在我眼里,更愿意把其看做网络黑社会,幕后黑手。根据目前所找到的资料,知其是80sec团队制作的网站,但是我对这个团队的性质表示不大信任。
December 27, 2011
| 作者:白菜
|
分类:编程算法
进程和线程这对概念的理解也是很难的,至今网络上可查的资料对其的理解出入都挺大,在不同的操作系统中,如linux和windows中,其概念和实现都是有出入的。因此,我在这里结合我自己的理解谈下这两个概念,讲的都是一般性的概念,并且主要是基以WINDOWS的。
一般将进程定义为一个正在运行的程序的实例。我们在任务管理器重所看到的每一项,就可以理解为一个进程,每个进程都有一个地址空间,这个地址空间里有可执行文件的代码和数据,以及线程堆栈等。一个程序至少有一个进程。进程可以创建子进程,创建的子进程可以和父进程一起工作,也可以独立运行。
而线程是隶属于进程的,也就是说,线程是不能单独存在的,线程存在于进程中。每个进程至少有一个主线程,进程里的线程就负责执行进程里的代码,这也叫做进程的“惰性”。线程所使用的资源是它所属的进程的资源。线程也有自己的资源,主要组成部分就是一些必要的计数器和线程栈,占用的资源很少。我们可以理解为进程就是个容器,而线程才是真正干活的。线程可以在内核空间实现,也可以在用户空间实现。
这里特别提一下linux,在linux中,每一个进程都必须有一个父进程(如果没有父进程,则把PID=1的根进程作为其父进程)。进程退出了,就成了僵尸进程,等待父进程的退出信号,如果父进程没有给他发信号,得给他找一个父进程,或者等待内核自动销毁。Linux内核只提供了轻量进程的支持,限制了更高效的线程模型的实现,但Linux着重优化了进程的调度开销,使得linux进程切换开销较小。所以linux系统没有真正意义上的线程机制。linux中,Linux线程是通过进程来实现,进程和线程是同一个层次的。(我这里提到的是基于linux 2.6 内核的,在最新的LINUX中,对线程的实现做了优化,但是还是基于进程的。有些linux实现了新的线程机制,但主流还是和我描述的一样)
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这也是进程和线程的重要区别。
比如,windows中的explorer就是资源管理器进程,我们每打开一个窗口,这个进程就会创建一个线程。有上面的描述,我们可以知道,进程有独立的地址空间,多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
在第一节中,我提到过,“并发!=多线程“,是的。如Unix操作系统和Windows操作系统支持多用户、多进程的并发执行,而Java这样的语言支持应用程序进程内部的多个执行线程的并发执行。我们知道,php在语法上是没有多线程这个概念的,那java/NET的多线程是怎么实现的。实际上,java只是通过native方法调用操作系统API来创建多线程而已。另外,我们经常说某个网站并发是多少多少,nginx是高并发服务器了。。这里的并发和我前面的并发概念是不同的。要想做到高并发,除了多线程等的实现,还需要各种好的IO机制,不然线程再多也没用,资源利用跟不上来。高并发又跟同步、异步等IO复用机制有了牵连。
下面列举一下进程和线程中的一些概念。
进程:创建,销毁,创建子进程(fork),优先级。
线程:创建,挂起,恢复,销毁,切换,协作,睡眠,唤醒,等待,同步,锁,优先级。
从上面的描述,基本可以理清进程和线程的关系了。最后总结下:
1.linux没有内核上的多线程实现,但是不能说linux没有多线程。WINDOWS有内核的多线程实现。
2.进程开销大,线程开销小,但是linux的进程和线程开销差异不明显。
3.并行的实现离不开多线程。
再留个悬念,多线程的实现和运行还会涉及到同步异步,原子性等概念。这些概念是相互交叉的,这里先不做深入。
December 27, 2011
| 作者:白菜
|
分类:编程算法
最近正想整理下这个专题,想了下,主要有以下几组概念,我们时不时会听到,但只要一细想就会觉得困惑,觉得没那么简单。这几组概念有:
1.并发和并行
2.线程与进程
3.缓冲与缓存
4.同步与异步
5.阻塞与非阻塞
6.原子性与事务性
这几组概念,不仅概念间容易混淆,而且一组概念和另一组概念之间又存在交叉,极容易搞混。
------------------------------------------------------------------------
下面,就我所了解的知识,分门别类的谈一下这几个概念间的关系,如有不当之处,还望指出和补充。
1.并发( concurrency)和并行( Parallelism)
狭义的并发和并行属于操作系统里的概念。并发和并行是两个即有相同处,又有区别的概念,相同的地方在于他们都是指同时处理多个任务。不同的地方在于并发是逻辑上的,并行是物理上真真实实的。
我们都清楚,在单个CPU的情况下,对于到达CPU执行单元的作业(作业就是待执行的进程和线程),都是需要排队的,因为我只有一个窗口,你要吃饭,就必须到我这个窗口来,也就是说CPU资源是有限的。假如说这个时候,某个作业一直占着CPU资源不放,那么其他作业就得不到执行,也就是被阻塞,被挂起了。这就需要一种机制,保证每个作业都有机会被执行到,这种机制就叫做处理器的调度机制,其中又分为抢占式和非抢占式的调度。细分后又有先来先服务(老老实实排队,谁先到就服务谁),最短作业优先(哪个好搞定就先服务谁),优先级调度,时间片调度等。并发就是通过时间片轮转的方式实现的,一个作业执行一段时间后被中断,换另外一个上去,大家都有机会被执行到。
时间片轮转,就涉及到一个进程切换的问题,频繁的切换必然带来性能开销。我们看到的一个程序一直在持续运行,但实际上并不是这样的,从CPU级别的粒度上讲,其实是多个程序间在快速的切换,只是这个切换的速度很快(想一下,CPU的主频现在都有2GHZ了),我们感觉不到而已。在早期的操作系统中,不支持多任务,一个任务在执行,其它任务就必须等待。
废话说了这么多,就是要说明,并发就是多个进程或线程在同时执行,但实际上,他们并不是真正的在同一个时间点上一起跑的,而是我跑一段时间后,接下来你跑,我再跑,看起来我们都在跑,这就是并发。
而并行,则是实实在在的多个进程同时跑在高速公路上,齐头并进。看下面的图,上面的是并发,下面的是并行。

通过我前面的描述,可以知道,并发是由CPU调度算法实现的,只需要一个CPU即可实现并发。而并行则要在多核或者多处理器情况下才能做到,单CPU是无法实现并行的,因为任一个时刻点上只有一个程序在处理器上运行。并行计算,这就是当今大型计算机所要实现的难题,如何让几千个CPU进行协作。并发和并行都需要处理器和OS的支持。
提到并发,就难免会提到多线程,并发!=多线程,多线程只是一种并发的实现模型。这里暂且留作后话。根据我们前面的描述,可以知道,时间片切换是有开销的,因此多线程不一定就能发挥出更佳的效率,特别是在单处理器的情况下。故,多线程也是不能盲目迷信的。
December 26, 2011
| 作者:白菜
|
分类:随便侃侃
目前为止,据我个人观察,从语法角度上讲,最让人困惑和难以理解的一种语言应该是LISP语言了。打开这种语言的代码,一眼望去,满目全是括弧,一层套一层。 这种语言特征最大的问题是,它不符合人们通常的思维习惯。LISP语言的这个特点是个整体的语言现象,而任何一种语言其实也都有一些个别的很奇特的东西, 其中有些会奇怪的让你摸不着头脑。有一个好事者在一个帖子上征集各种语言里不合常理的地方,结果收集到了320多条,问题最大最多的语言算是Javascript了,另外还有C,Java,Python,PHP等等。下面列出的是其中最有趣的几条。
1. 在C语言里,数组可以这样索引取值:
a[10]
这种写法相当常见。
然而,还有一种很少见的写法(绝对可用!)是这样的:
10[a]
解释:
2.这两种写法的效果是一样的。
在JavaScript里:
'5' + 3 结果是 '53'
而
'5' - 3 结果是 2
解释:在ECMASCRIPT规范里定义了字符串的加法,但没有字符串减法。11.6.1里有
7. If Type(lprim) is String or Type(rprim) is String, then a. Return the String that is the result of concatenating ToString(lprim) followed by ToString(rprim)
阅读剩余部分...
December 23, 2011
| 作者:白菜
|
分类:随便侃侃
为了应对类似CSDN密码泄露这样的事件可能带来的影响,特意做了个密码管理器
,输入域名点击“OK”,对每个域名生成经过特殊编码后的密码,再经过一系列处理后自动复制到剪切板。
没啥技术含量,只是提供给和我一样蛋疼的人一个蛋疼的想法。
另外,推荐一下,“奥巴码-aobama(
http://aobama.googlecode.com)”。不是美国总统的那个奥巴马,而是一个JAVA写的编码解码的小工具,我感觉其很好,好用也好玩。我使用了他的代码。