Java 系列博客文章

NIO 各种使用注意点

找问题的时后发现了这篇文章,惊为天人,几乎涵盖了我所有碰到的坑,非常不错!

不得不说,NIO的API设计的够难用的,坑还巨多....这也是为什么大家都不直接使用nio的原因吧,一般会用mina或者netty啥的(这是个记录的博客,所以会不断更新)

关于通道本身的一些注意点,请参考我之前的:nio通道(2)---几个注意点

其他一些参考nio summary

1 坑爹的事件

SocetChannel和ServerSocketChannel各自支持的事件,在前面已经提到:

http://www.360doc.com/content/12/0902/17/495229_233773276.shtml

1.1 什么时候可以register

客户端:
linux:SocketChannel: 注册了op_read,op_write,op_connect的SocketChannel在connect之前,open之后,都可以select到,只不过不能够read和write
windows:SocketChannel:只有注册了op_connect的SocketChannel在connect之后,才被select到。是一个正确的符合逻辑的理解

服务器端:

(2)如果只注册了读的操作,则select时,会发生阻塞,因为是没有为读准备好的socket
(3)如果没有可写的socket,则select时,不会发生阻塞,直接返回0。如果阻塞写,只能是发送区满。

另外:iterator到selectedKey之后,需要将该key移除出selectedKey。如果不移出,例如OP_ACCEPT,则再下次accept之后,会产生空的SocketChannel。

1.2 connect事件(连接--成功or失败?)

在之前的Socket通道中,已经看到,非阻塞模式下,connect操作会返回false,后面会发出CONNECT事件来表示连接,但是这里其实没有区分成功还是失败。。

connect事件:表示连接通道连接就绪或者发生了错误,会被加到ready 集合中(下面面是API说明)

If the selector detects that the corresponding socket channel is ready to complete its connection sequence, or has an error pending, then it will add OP_CONNECT to the key's ready set and add the key to its selected-key set.

所以这个事件发生的时候不能简单呢的认为连接成功,要使用finishConnect判断下,如果连接失败,会抛出异常

NIO就绪处理之OP_CONNECT

if (key.isValid() && key.isConnectable()) { SocketChannel ch = (SocketChannel) key.channel(); if (ch.finishConnect()) { // Connect successfully // key.interestOps(SelectionKey.OP_READ); } else { // Connect failed } } 1.3 read&关闭

一直很奇怪,为啥没有close事件,终于在一次实验的时候发现:

1.启动一个客户端和服务端

2.关闭客户端,服务端会发生一个read事件,并且在read的时候抛出异常,来表示关闭

另外,这个事件会不断发生,就算从已准备好的集合移除也没有,必须将该channel关闭或者调用哪个该key的cancel方法,因为SelectionKey代表的是Selector和Channel之间的联系,所以在Channel关闭了之后,对于Selector来说,这个Channel永远都会发出关闭这个事件,表明自己关闭了,直到从该Selector移除去

3.服务端关闭,client端在write的时候会抛出异常

java.io.IOException: 远程主机强迫关闭了一个现有的连接。

at sun.nio.ch.SocketDispatcher.write0(Native Method)

at sun.nio.ch.SocketDispatcher.write(Unknown Source)

1.4 还是关闭(TIME_WAIT)

NIO的SelectableChannel关闭的一个问题

如果在取消SelectionKey(这时候只是加入取消的键集合,下一次select才会执行)后没有调用到selector的select方法(因为Client一般在取消key后,我们都会终止调用select的循环,当然,server关闭一个注册的channel我们是不会终止select循环的),那么本地socket将进入CLOSE-WAIT状态(等待本地Socket关闭)。简单的解决办法是在 SelectableChannel.close方法之后调用Selector.selectNow方法

Netty在超过256连接关闭的时候主动调用一次selectNow

1.5 write

NIO就绪处理之OP_WRITE

一开始很多人以为write事件,表示在调用channel的write方法之后,就会发生这个事件,然后channel再会把数据真正写出,但是实际上,**写操作的就绪条件为底层缓冲区有空闲空间,而写缓冲区绝大部分时间都是有空闲空间的,所以当你注册写事件后,写操作一直是就绪的,选择处理线程全占用整个CPU资源。所以,只有当你确实有数据要写时再注册写操作,并在写完以后马上取消注册,**一般的,Client端需要注册OP_CONNECT,OP_READ;Server端需要注册OP_ACCEPT并且连接之后注册OP_READ

当有数据在写时,将数据写到缓冲区中,并注册写事件。

[java]view plaincopy

public void write(byte[] data) throws IOException { writeBuffer.put(data); key.interestOps(SelectionKey.OP_WRITE); }

注册写事件后,写操作就绪,这时将之前写入缓冲区的数据写入通道,并取消注册。[java]view plaincopy

大部分情况下,其实直接用write方法写就好了,没必要用写事件。

另外,关于write还可以参考下面的4.2的一些注意点

2 Channel的bind方法

Socket/ServerSocket

两者都有bind方法,表示绑定到某个端口,在绑定之后,前者调用connect方法,表示连接到某个服务端;后者要在后面调用accept方法,监听到来的连接请求(一个Socket句柄包含了两个地址对,本地ip:port----远程ip:port)

3 Selector的select和wakeup机制

Java NIO类库Selector机制解析(上)
Java NIO类库Selector机制解析(下)
Java NIO 类库Selector机制解析(续)

在windows平台下,调用Selector.open()方法,会自己和自己建立两条TCP连接,消耗了两个TCP连接和端口,也消耗了文件描述符

在linux平台下,会自己和自己建立两条管道,消耗了两个系统的文件描述符

一个阻塞seelct上的线程想要被唤醒,有3种方式:

1.有数据可读/.可写,或者出现异常

2.阻塞时间到,time out

3.收到一个non-block信号,由kill或者pthread_kill发出

这两个方法完全是来模仿Linux中的的kill和pthread_kill给阻塞在select上的线程发信号的。但因为发信号这个东西并不是一个跨平台的标准(pthread_kill这个系统调用也不是所有Unix/Linux都支持的),而pipe是所有的Unix/Linux所支持的,但Windows又不支持,所以,Windows用了TCP连接来实现这个事。(在Linux下使用pipe管道)

4 一些特殊情况 4.1 读不满

因为我们的数据都是偏业务性的,比如使用开头一个字节来表示后面数据的长度,接着就会等待读取到那么多数据,但是TCP是流式的协议,100字节的数据可能是一段段发送过来的,所以在没有读到完整的数据前需要等待。

这时候可以将buffer attach到key上,下次read发生的时候再继续读取,但是也有另外一种说法,在网络条件比较好的情况下,直接使用一个临时selector会减少上下文切换。。这个不太明白

4.2 写不出去

在发送缓冲区空间不够的情况下,write方法可能会返回能够写出去的字节数,比如只剩50字节,你写入100字节,这时候write会返回50,即往缓冲区写入了50字节

在网络较好的情况下,这应该是不太可能发生的,一般都是网络有问题,重传率很高

详细的情况可以参考:java nio对OP_WRITE的处理解决网速慢的连接

while (bb.hasRemaining()) { int len = socketChannel.write(bb); if (len < 0) { throw new EOFException(); } }

由于缓冲区一直蛮,下面的代码会一直执行,占用CPU100%,因此推荐的方式如下

while (bb.hasRemaining()) { int len = socketChannel.write(bb); if (len < 0) { throw new EOFException(); } if (len == 0) { selectionKey.interestOps( selectionKey.interestOps() | SelectionKey.OP_WRITE); mainSelector.wakeup(); break; } }

如果返回0,表示缓冲区满,那么注册WRITE事件,缓冲区不满的情况下,就会触发WRITE事件,在那时候再写入,可以避免不要的消耗。(另外Grizzly还是用了另一种方式,也可以从上面的参考链接得到)

原文:https://blog.csdn.net/asdasdasd123123123/article/details/88253912

read more

Linux、网络相关

linux内存映射mmap原理分析

内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。

二、基本函数

mmap函数是unix/linux下的系统调用,详细内容可参考《Unix Netword programming》卷二12.2节。

mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。mmap并不分配空间, 只是将文件映射到调用进程的地址空间里(但是会占掉你的 virutal memory), 然后你就可以用memcpy等操作写文件, 而不用write()了.写完后,内存中的内容并不会立即更新到文件中,而是有一段时间的延迟,你可以调用msync()来显式同步一下, 这样你所写的内容就能立即保存到文件里了.这点应该和驱动相关。 不过通过mmap来写文件这种方式没办法增加文件的长度, 因为要映射的长度在调用mmap()的时候就决定了.如果想取消内存映射,可以调用munmap()来取消内存映射。

void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)

mmap用于把文件映射到内存空间中,简单说mmap就是把一个文件的内容在内存里面做一个映像。映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。

原理

首先,“映射”这个词,就和数学课上说的“一一映射”是一个意思,就是建立一种一一对应关系,在这里主要是只 硬盘上文件 的位置与进程 逻辑地址空间 中一块大小相同的区域之间的一一对应,如图1中过程1所示。这种对应关系纯属是逻辑上的概念,物理上是不存在的,原因是进程的逻辑地址空间本身就是不存在的。在内存映射的过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存,具体到代码,就是建立并初始化了相关的数据结构(struct address_space),这个过程有系统调用mmap()实现,所以建立内存映射的效率很高。

图1.内存映射原理

既然建立内存映射没有进行实际的数据拷贝,那么进程又怎么能最终直接通过内存操作访问到硬盘上的文件呢?那就要看内存映射之后的几个相关的过程了。

mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无需再调用read或write对文件进行读写,而只需要通过ptr就能够操作文件。但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成物理地址,如图1中过程2所示。这个过程与内存映射无关。

前面讲过,建立内存映射并没有实际拷贝数据,这时,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中,如图1中过程3所示。这个过程与内存映射无关。

如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上,如图1中过程4所示。这个过程也与内存映射无关。

效率

从代码层面上看,从硬盘上将文件读入内存,都要经过文件系统进行数据拷贝,并且数据拷贝操作是由文件系统和硬件驱动实现的,理论上来说,拷贝数据的效率是一样的。但是通过内存映射的方法访问硬盘上的文件,效率要比read和write系统调用高,这是为什么呢?原因是read()是系统调用,其中进行了数据拷贝,它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区,如图2中过程1,然后再将这些数据拷贝到用户空间,如图2中过程2,在这个过程中,实际上完成了 两次数据拷贝 ;而mmap()也是系统调用,如前所述,mmap()中没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了 一次数据拷贝 。因此,内存映射的效率要比read/write效率高。

图2.read系统调用原理

下面这个程序,通过read和mmap两种方法分别对硬盘上一个名为“mmap_test”的文件进行操作,文件中存有10000个整数,程序两次使用不同的方法将它们读出,加1,再写回硬盘。通过对比可以看出,read消耗的时间将近是mmap的两到三倍。

#include<unistd.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<sys/stat.h> #include<sys/time.h> #include<fcntl.h> #include<sys/mman.h> #define MAX 10000 int main() { int i=0; int count=0, fd=0; struct timeval tv1, tv2; int *array = (int *)malloc( sizeof(int)*MAX ); /*read*/ gettimeofday( &tv1, NULL ); fd = open( "mmap_test", O_RDWR ); if( sizeof(int)*MAX != read( fd, (void *)array, sizeof(int)*MAX ) ) { printf( "Reading data failed.../n" ); return -1; } for( i=0; i<MAX; ++i ) ++array[ i ]; if( sizeof(int)*MAX != write( fd, (void *)array, sizeof(int)*MAX ) ) { printf( "Writing data failed.../n" ); return -1; } free( array ); close( fd ); gettimeofday( &tv2, NULL ); printf( "Time of read/write: %dms/n", tv2.tv_usec-tv1.tv_usec ); /*mmap*/ gettimeofday( &tv1, NULL ); fd = open( "mmap_test", O_RDWR ); array = mmap( NULL, sizeof(int)*MAX, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 ); for( i=0; i<MAX; ++i ) ++array[ i ]; munmap( array, sizeof(int)*MAX ); msync( array, sizeof(int)*MAX, MS_SYNC ); free( array ); close( fd ); gettimeofday( &tv2, NULL ); printf( "Time of mmap: %dms/n", tv2.tv_usec-tv1.tv_usec ); return 0; }

输出结果:

Time of read/write: 154ms

Time of mmap: 68ms

原文:https://blog.csdn.net/mg0832058/article/details/5890688

read more

html/css/js/各种前端框架/工具

MySQL的InnoDB的幻读问题

MySQL InnoDB事务的隔离级别有四级,默认是“可重复读”(REPEATABLE READ)。

未提交读(READ UNCOMMITTED)。另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据(脏读)。 提交读(READ COMMITTED)。本事务读取到的是最新的数据(其他事务提交后的)。问题是,在同一个事务里,前后两次相同的SELECT会读到不同的结果(不重复读)。 可重复读(REPEATABLE READ)。在同一个事务里,SELECT的结果是事务开始时时间点的状态,因此,同样的SELECT操作读到的结果会是一致的。但是,会有幻读现象(稍后解释)。 串行化(SERIALIZABLE)。读操作会隐式获取共享锁,可以保证不同事务间的互斥。

四个级别逐渐增强,每个级别解决一个问题。

脏读,最容易理解。另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据。 不重复读。解决了脏读后,会遇到,同一个事务执行过程中,另外一个事务提交了新数据,因此本事务先后两次读到的数据结果会不一致。 幻读。解决了不重复读,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)。但是,如果另一个事务同时提交了新数据,本事务再更新时,就会“惊奇的”发现了这些新数据,貌似之前读到的数据是“鬼影”一样的幻觉。

借鉴并改造了一个搞笑的比喻:

脏读。假如,中午去食堂打饭吃,看到一个座位被同学小Q占上了,就认为这个座位被占去了,就转身去找其他的座位。不料,这个同学小Q起身走了。事实:该同学小Q只是临时坐了一小下,并未“提交”。 不重复读。假如,中午去食堂打饭吃,看到一个座位是空的,便屁颠屁颠的去打饭,回来后却发现这个座位却被同学小Q占去了。 幻读。假如,中午去食堂打饭吃,看到一个座位是空的,便屁颠屁颠的去打饭,回来后,发现这些座位都还是空的(重复读),窃喜。走到跟前刚准备坐下时,却惊现一个恐龙妹,严重影响食欲。仿佛之前看到的空座位是“幻影”一样。

一些文章写到InnoDB的可重复读避免了“幻读”(phantom read),这个说法并不准确。

做个试验:(以下所有试验要注意存储引擎和隔离级别)

mysql> show create table t_bitfly\G;
CREATE TABLE t_bitfly (
id bigint(20) NOT NULL default '0',
value varchar(32) default NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=gbk

mysql> select @@global.tx_isolation, @@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+

试验一:

t Session A                                   Session B
|
| START TRANSACTION;            START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| empty set
|                                                             INSERT INTO t_bitfly
|                                                             VALUES (1, 'a');
|
| SELECT * FROM t_bitfly;
| empty set
|                                                             COMMIT;
|
| SELECT * FROM t_bitfly;
| empty set
|
| INSERT INTO t_bitfly VALUES (1, 'a');
| ERROR 1062 (23000):
| Duplicate entry '1' for key 1
v (shit, 刚刚明明告诉我没有这条记录的)

如此就出现了幻读,以为表里没有数据,其实数据已经存在了,傻乎乎的提交后,才发现数据冲突了。

试验二:

t Session A                                     Session B
|
| START TRANSACTION;              START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                                                            INSERT INTO t_bitfly
|                                                            VALUES (2, 'b');
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                                                            COMMIT;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|
| UPDATE t_bitfly SET value='z';
| Rows matched: 2  Changed: 2  Warnings: 0
| (怎么多出来一行)
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | z     |
| |    2 | z     |
| +------+-------+
|
v

本事务中第一次读取出一行,做了一次更新后,另一个事务里提交的数据就出现了。也可以看做是一种幻读。

那么,InnoDB指出的可以避免幻读是怎么回事呢?

http://dev.mysql.com/doc/refman/5.0/en/innodb-record-level-locks.html

By default, InnoDB operates in REPEATABLE READ transaction isolation level and with the innodb_locks_unsafe_for_binlog system variable disabled. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows (see Section 13.6.8.5, “Avoiding the Phantom Problem Using Next-Key Locking”).

准备的理解是,当隔离级别是可重复读,且禁用innodb_locks_unsafe_for_binlog的情况下,在搜索和扫描index的时候使用的next-key locks可以避免幻读。

关键点在于,是InnoDB默认对一个普通的查询也会加next-key locks,还是说需要应用自己来加锁呢?如果单看这一句,可能会以为InnoDB对普通的查询也加了锁,如果是,那和序列化(SERIALIZABLE)的区别又在哪里呢?

MySQL manual里还有一段:

13.2.8.5. Avoiding the Phantom Problem Using Next-Key Locking (http://dev.mysql.com/doc/refman/5.0/en/innodb-next-key-locking.html)

To prevent phantoms, InnoDB uses an algorithm called next-key locking that combines index-row locking with gap locking.

You can use next-key locking to implement a uniqueness check in your application: If you read your data in share mode and do not see a duplicate for a row you are going to insert, then you can safely insert your row and know that the next-key lock set on the successor of your row during the read prevents anyone meanwhile inserting a duplicate for your row. Thus, the next-key locking enables you to “<span style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px;">lock</span>” the nonexistence of something in your table.

我的理解是说,InnoDB提供了next-key locks,但需要应用程序自己去加锁。manual里提供一个例子:

SELECT * FROM child WHERE id > 100 FOR UPDATE;

这样,InnoDB会给id大于100的行(假如child表里有一行id为102),以及100-102,102+的gap都加上锁。

可以使用show innodb status来查看是否给表加上了锁。

再看一个实验,要注意,表t_bitfly里的id为主键字段。实验三:

t Session A                                       Session B
|
| START TRANSACTION;                START TRANSACTION;
|
| SELECT * FROM t_bitfly
| WHERE id<=1
| FOR UPDATE;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                                                           INSERT INTO t_bitfly
|                                                           VALUES (2, 'b');
|                                                           Query OK, 1 row affected
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                                                           INSERT INTO t_bitfly
|                                                           VALUES (0, '0');
|                                                           (waiting for lock ...
|                                                           then timeout)
|                                                           ERROR 1205 (HY000):
|                                                           Lock wait timeout exceeded;
|                                                           try restarting transaction
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                                                           COMMIT;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
v

可以看到,用id<=1加的锁,只锁住了id<=1的范围,可以成功添加id为2的记录,添加id为0的记录时就会等待锁的释放。

MySQL manual里对可重复读里的锁的详细解释:

http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read

For locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE),UPDATE, and DELETE statements, locking depends on whether the statement uses a unique index with a unique search condition, or a range-type search condition. For a unique index with a unique search condition, InnoDB locks only the index record found, not the gap before it. For other search conditions, InnoDB locks the index range scanned, using gap locks or next-key (gap plus index-record) locks to block insertions by other sessions into the gaps covered by the range.

一致性读和提交读,先看实验,实验四:

t Session A                                                    Session B
|
| START TRANSACTION;                             START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| +----+-------+
|                                                                        INSERT INTO t_bitfly
|                                                                                VALUES (2, 'b');
|                                                                        COMMIT;
|
| SELECT * FROM t_bitfly;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| +----+-------+
|
| SELECT * FROM t_bitfly LOCK IN SHARE MODE;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| |  2 | b     |
| +----+-------+
|
| SELECT * FROM t_bitfly FOR UPDATE;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| |  2 | b     |
| +----+-------+
|
| SELECT * FROM t_bitfly;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| +----+-------+
v

如果使用普通的读,会得到一致性的结果,如果使用了加锁的读,就会读到“最新的”“提交”读的结果。

本身,可重复读和提交读是矛盾的。在同一个事务里,如果保证了可重复读,就会看不到其他事务的提交,违背了提交读;如果保证了提交读,就会导致前后两次读到的结果不一致,违背了可重复读。

可以这么讲,InnoDB提供了这样的机制,在默认的可重复读的隔离级别里,可以使用加锁读去查询最新的数据。

http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html

If you want to see the “freshest” state of the database, you should use either the READ COMMITTED isolation level or a locking read:
SELECT * FROM t_bitfly LOCK IN SHARE MODE;

结论:MySQL InnoDB的可重复读并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是next-key locks。

==================== 结尾 ====================

作者: bitfly. 转载请注明来源或包含本信息. 谢谢
链接: http://blog.bitfly.cn/post/mysql-innodb-phantom-read/

read more
APK反编译总结
1.准备环境

win7
android-sdk_r24.0.2-windows.zip
jdk7
android studio1.5
eclipse
charles

温馨提示:后面三个工具都需要jre|jdk环境,请首先安装jdk.

2.抓包过程

通过在PC端安装charles软件,android端设置网络代理,抓取网络数据包。

2.1、PC端:在pc端创建wifi热点共享给外设->CMD命令行

netsh wlan set hostednetwork mode=allow ssid=abcd key=abcd1234

选择正在使用的网络连接,右键共享->勾选并选择刚刚创建的热点连接

netsh wlan start hostednetwork

charles:proxy setting 设置代理端口,若需https抓包请设置ssl选项,并且客户端安装charles证书

2.2、客户端:WLAN设置刚创建的“abcd”共享,并指定代理IP和端口号(自己ipconfig查看即可)

3.准备反编译工具

主要针对jvm的class文件和android虚拟机字节码smali,所需软件如下:

apktool_2.0.0rc4.zip ---- 可以得到apk里的资源和smali文件 dex2jar-2.0.zip ---- 获得class文件 jd-gui.exe ---- 反解class文件 signapk.rar ---- 修改smali或者资源文件,重新打包签名,***DEBUG*** 4.开始吧

这里以反编译土豆 app为例:

得到res和smali

java -jar apktool.jar d -d ..\..\youku\tudou\tudoushipin_61.apk -o ..\..\youku\tudou\tudoushipin_61

得到class

dex2jar.bat tudoushipin_61.apk

对上面的class使用jd-gui反编译,并导入eclipse

5.上演调试 && android studio

将smali文件导入到android studio

5.1、找到刚才apktool反解的目录找到AndroidManifest.xml,LAUNCHER对应的Activity标签上加入可被debug的配置android:debuggable="true",并保存。

5.2、假设我们现在把断点加载app的启动入口:
找到APK的入口Activity类(搜索关键字LAUNCHER你懂得),也就是:com.tudou.ui.activity.WelcomeActivity。

到了关键性的一步,找到这个Activity对应的smali文件;
定位到入口方法:onCreate;
在下面加入DEBUG代码,app启动时加入断点会停在这个位置;
说明一下:这段代码是smali的语法更多了解可以自行Google,OK。

a=0;// invoke-static {}, Landroid/os/Debug;->waitForDebugger()V

说明:根据你的需要可以把断点加到任意位置,前提是你要知道它在对应的smali文件的哪一行:方法是拿反编译后的Java文件和smali对应着去看,然后再找;后面的DEBUG也是这个思路(剧透)。

5.3、对修改后的apk重新打包

i.重新打包:

java -jar apktool.jar b -d ..\..\youku\tudou\tudoushipin_61 -o debug-tudou.apk

ii.重新签名:

java -jar signapk\signapk.jar signapk\testkey.x509.pem signapk\testkey.pk8 debug-tudou.apk debug-tudou.sign.apk

iii.一切可能都不是那么顺利):(

5.4、开启android studio-->基于知名的IntelliJ IDEA开发

1.导入之前反编译得到的smali文件到android studio,并在‘前面加debug代码’的地方加入断点。
2.找一部android手机(模拟器就算了,又慢又总是不兼容),安装刚才的签名后的apk,通过USB数据线接入PC。

5.5、有一些必要的说明

1.默认安装完android studio,例如:C:\dev\android\sdk
2.对于android Dalvik虚拟机的调试监控,DDMS已经被废弃了,新的是tools下的monitor工具,将其启动
3.在monitor中会看到devices中会出现小手机图标,端口号一般是8600

6.开始远程调试

1.android studio中菜单栏->RUN->Edit Configuration -> Remote(这根在eclipse中差不多)
指定host:localhost,端口:8600,module:smali所在的位置
启动app-->运行debug即可 -> 顺利的话光标会定位到你刚才的断点处。

2.观察Android Monitor窗口
观察Debugger tag,可以查看对象和变量的值

@hell 分享

read more

交流讨论专区,各种水~

SVN 自动删除 “被手动删除” 的文件

删除SVN存储库工作副本中的文件时,应该在命令行上执行:

svn rm [filename]

但是,如果您不这样做(例如通过gui删除,或者只执行“rm”而不执行“svn”),那么svn就会感到困惑,并将一个“!”在所有被删除的文件之前的状态。

如果您进行svn更新,所有的文件都将被恢复,显然您花费在删除它们上的所有时间都将被浪费了。

你真的应该使用svn rm,但如果已经太晚了,你可以使用这个bash脚本来删除svn中的文件:

#从svn中删除你已经删除的文件 svn status | grep "^\!" | sed 's/^\! *//g' | xargs svn rm

这个命令执行一个status命令,查找所有以“!”开头的行。然后提取文件名,并运行它通过“ svn rm ” 真正删除文件。

警告:确保您确实想删除所有这些文件!

使用方法自负风险。代码足够简单,所以您应该能够了解它的功能。

read more
2019-02-26 听力打卡《参观自由女神像》

链接:https://dict.eudic.net/webting/desktopplay?id=0aa666b0-148d-11e9-8449-000c29ffef9b&token=QYN+eyJ0b2tlbiI6IiIsInVzZXJpZCI6IiIsInVybHNpZ24iOiJuOEc3d05JUmIxYlhsK3dFdzRQSXVVNnZzaVk9IiwidCI6IkFCSU1UVTNNRGt4TmpFM09BPT0ifQ%3D%3D

Ladies and Gentlemen, this is the Statue of Liberty , it's one of the American symbols.
It's really spectacular.
The statue has for a century acted as a figurehead for the American Dream.
I think we can climb to the top, Can't we ?
Of course you can.

词串:

spectacular
英 /spek'tækjʊlə/ 美 /spɛk'tækjəlɚ/ adj. 壮观的,惊人的;公开展示的 has for a century / have for a century
已经有一个世纪了 acted as a figurehead for American Dream.
被认为是美国梦的象征。 climb to
爬到

复习:
2019-02-28

read more